Full Code of halzhang/StartupNews for AI

master 05b04cec88ab cached
184 files
318.2 KB
78.2k tokens
654 symbols
1 requests
Download .txt
Showing preview only (371K chars total). Download the full file or copy to clipboard to get everything.
Repository: halzhang/StartupNews
Branch: master
Commit: 05b04cec88ab
Files: 184
Total size: 318.2 KB

Directory structure:
gitextract_me0tpa6o/

├── .gitignore
├── .travis.yml
├── 3rdlib/
│   ├── commonlog/
│   │   ├── AndroidManifest.xml
│   │   ├── README.md
│   │   ├── build.gradle
│   │   ├── build.xml
│   │   ├── proguard-project.txt
│   │   ├── project.properties
│   │   └── src/
│   │       └── com/
│   │           └── halzhang/
│   │               └── android/
│   │                   └── common/
│   │                       └── CDLog.java
│   └── commontoast/
│       ├── AndroidManifest.xml
│       ├── README.md
│       ├── build.gradle
│       ├── build.xml
│       ├── proguard-project.txt
│       ├── project.properties
│       └── src/
│           └── com/
│               └── halzhang/
│                   └── android/
│                       └── common/
│                           └── CDToast.java
├── README.md
├── android-wait-for-emulator
├── app/
│   ├── build.gradle
│   ├── libs/
│   │   └── libGoogleAnalyticsServices.jar
│   ├── proguard-project.txt
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── halzhang/
│           │           └── android/
│           │               └── apps/
│           │                   └── startupnews/
│           │                       ├── ApplicationModule.java
│           │                       ├── Constants.java
│           │                       ├── MyApplication.java
│           │                       ├── SnApiComponent.java
│           │                       ├── analytics/
│           │                       │   ├── MyExceptionParser.java
│           │                       │   └── Tracker.java
│           │                       ├── presenter/
│           │                       │   ├── BasePresenter.java
│           │                       │   ├── BaseView.java
│           │                       │   ├── CommentsListContract.java
│           │                       │   ├── CommentsListPresenter.java
│           │                       │   ├── CommentsListPresenterModule.java
│           │                       │   ├── DiscussComponent.java
│           │                       │   ├── DiscussContract.java
│           │                       │   ├── DiscussPresenter.java
│           │                       │   ├── DiscussPresenterModule.java
│           │                       │   ├── LoginComponent.java
│           │                       │   ├── LoginContract.java
│           │                       │   ├── LoginPresenter.java
│           │                       │   ├── LoginPresenterModule.java
│           │                       │   ├── MainActivityContract.java
│           │                       │   ├── MainActivityPresenter.java
│           │                       │   ├── MainActivityPresenterModule.java
│           │                       │   ├── MainComponent.java
│           │                       │   ├── NewsListContract.java
│           │                       │   ├── NewsListFragmentComponent.java
│           │                       │   ├── NewsListPresenter.java
│           │                       │   └── NewsListPresenterModule.java
│           │                       ├── ui/
│           │                       │   ├── AboutActivity.java
│           │                       │   ├── BaseActivity.java
│           │                       │   ├── BaseFragmentActivity.java
│           │                       │   ├── DiscussActivity.java
│           │                       │   ├── LauncherActivity.java
│           │                       │   ├── LoginActivity.java
│           │                       │   ├── MainActivity.java
│           │                       │   ├── ShareHelper.java
│           │                       │   ├── SubmitActivity.java
│           │                       │   ├── fragment/
│           │                       │   │   ├── BaseFragment.java
│           │                       │   │   ├── CommentsListFragment.java
│           │                       │   │   ├── LoginFragment.java
│           │                       │   │   ├── NewsListFragment.java
│           │                       │   │   └── SwipeRefreshRecyclerFragment.java
│           │                       │   ├── phone/
│           │                       │   │   └── BrowseActivity.java
│           │                       │   ├── tablet/
│           │                       │   │   ├── BrowseFragment.java
│           │                       │   │   └── DiscussFragment.java
│           │                       │   └── widgets/
│           │                       │       ├── CardViewDividerDecoration.java
│           │                       │       ├── DividerDecoration.java
│           │                       │       ├── ObservableWebView.java
│           │                       │       ├── SwitchPreference.java
│           │                       │       └── WebViewController.java
│           │                       └── utils/
│           │                           ├── ActivityScoped.java
│           │                           ├── ActivityUtils.java
│           │                           ├── AppUtils.java
│           │                           ├── CrashHandler.java
│           │                           ├── CustomTabsActivityHelper.java
│           │                           ├── CustomTabsHelper.java
│           │                           ├── DateUtils.java
│           │                           ├── FragmentScoped.java
│           │                           ├── PreferenceUtils.java
│           │                           └── UIUtils.java
│           └── res/
│               ├── anim/
│               │   ├── push_down_out.xml
│               │   ├── push_up_in.xml
│               │   ├── slide_in_left.xml
│               │   ├── slide_in_right.xml
│               │   ├── slide_out_left.xml
│               │   └── slide_out_right.xml
│               ├── drawable/
│               │   ├── action_bar_background.xml
│               │   ├── background_boardless.xml
│               │   ├── background_holo_dark.xml
│               │   ├── background_holo_light.xml
│               │   ├── bg_discuss_article.xml
│               │   ├── fragment_shadow.xml
│               │   ├── send_button_selector.xml
│               │   └── sidebar_shadow.xml
│               ├── layout/
│               │   ├── actionbar_indeterminate_progress.xml
│               │   ├── activity_browse.xml
│               │   ├── activity_discuss.xml
│               │   ├── activity_login.xml
│               │   ├── activity_main.xml
│               │   ├── browse_bar.xml
│               │   ├── comment_list_item.xml
│               │   ├── discuss_comment_item.xml
│               │   ├── discuss_header_view.xml
│               │   ├── fragment_browse.xml
│               │   ├── fragment_discuss.xml
│               │   ├── fragment_login.xml
│               │   ├── news_list_item.xml
│               │   ├── preference_toolbar.xml
│               │   ├── ptr_list_layout.xml
│               │   └── refresh_recycler_view_layout.xml
│               ├── menu/
│               │   ├── activity_browse.xml
│               │   ├── activity_login.xml
│               │   ├── activity_main.xml
│               │   ├── fragment_browse.xml
│               │   ├── fragment_discuss.xml
│               │   └── fragment_news.xml
│               ├── values/
│               │   ├── analytics.xml
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── ids.xml
│               │   ├── integers.xml
│               │   ├── strings.xml
│               │   └── themes.xml
│               └── xml/
│                   └── preferences.xml
├── build.gradle
├── buildsystem/
│   └── dependencies.gradle
├── changelog.md
├── data/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── halzhang/
│       │               └── android/
│       │                   └── startupnews/
│       │                       └── data/
│       │                           └── ApplicationTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── halzhang/
│           │           └── android/
│           │               └── startupnews/
│           │                   └── data/
│           │                       ├── Constant.java
│           │                       ├── CookieFactoryModule.java
│           │                       ├── JsoupConnectorModule.java
│           │                       ├── OkHttpClientModule.java
│           │                       ├── SessionManagerModule.java
│           │                       ├── SnApiModule.java
│           │                       ├── entity/
│           │                       │   ├── SNComment.java
│           │                       │   ├── SNComments.java
│           │                       │   ├── SNDiscuss.java
│           │                       │   ├── SNFeed.java
│           │                       │   ├── SNNew.java
│           │                       │   ├── SNSession.java
│           │                       │   ├── SNUser.java
│           │                       │   └── Status.java
│           │                       ├── exception/
│           │                       │   ├── LoginException.java
│           │                       │   ├── NetworkException.java
│           │                       │   └── SessionExpiredException.java
│           │                       ├── net/
│           │                       │   ├── ISnApi.java
│           │                       │   ├── JsoupConnector.java
│           │                       │   └── SnApiImpl.java
│           │                       ├── parser/
│           │                       │   ├── BaseHTMLParser.java
│           │                       │   ├── SNCommentsParser.java
│           │                       │   ├── SNCommentsParserV1.java
│           │                       │   ├── SNDiscussParser.java
│           │                       │   └── SNFeedParser.java
│           │                       └── utils/
│           │                           ├── CookieFactoryImpl.java
│           │                           ├── NetworkUtils.java
│           │                           ├── OkHttpClientHelper.java
│           │                           ├── PersistentCookieStore.java
│           │                           ├── PrefUtils.java
│           │                           ├── SerializableCookie.java
│           │                           └── SessionManager.java
│           └── res/
│               └── values/
│                   └── strings.xml
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release/
│   ├── StartupNews-release-0.1.apk
│   ├── StartupNews-release-0.2.0.0.apk
│   ├── StartupNews-release-0.3.0.0.apk
│   ├── StartupNews-release-0.3.1.0.apk
│   ├── StartupNews-release-0.4.0.0.apk
│   ├── StartupNews-release-0.5.0.0.apk
│   ├── StartupNews-release-0.5.1.0.apk
│   ├── StartupNews-release-0.5.2.0.apk
│   ├── StartupNews-release-0.6.0.0.apk
│   ├── StartupNews-release-1.0.0.0.apk
│   ├── StartupNews-release-1.0.1.0.apk
│   └── StartupNews-release.apk
├── settings.gradle
└── wait_for_emulator.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# built application files
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
bin/
gen/

# Local configuration file (sdk path, etc)
local.properties

# Eclipse project files
.classpath
.project
.settings

#build
ant.properties
*.keystore
StartupNews/app/src/main/res/values/analytics.xml

# IntelliJ files
.idea
*.iml
out/
module_*.xml

#gradle
build/
.gradle/

================================================
FILE: .travis.yml
================================================
language: android
jdk:
  - openjdk7
  - oraclejdk7
  - oraclejdk8
  
env:
  matrix:
    - ANDROID_TARGET=android-23  ANDROID_ABI=armeabi-v7a

android:
  components:
    - tools
    - platform-tools
    - build-tools-23.0.1
    - android-23
    - extra-android-support
    - extra-google-google_play_services
    - extra-google-m2repository
    - extra-android-m2repository

notifications:
  email: true

# Turn off caching to avoid any caching problems
cache: false
# Use the Travis Container-Based Infrastructure (see #203)
sudo: false

before_script:
  - echo no | android create avd --force -n test -t android-19 --abi armeabi-v7a
  - emulator -avd test -no-skin -no-audio -no-window &
  - android-wait-for-emulator
  - adb shell input keyevent 82 &

script: ./gradlew assembleDebug connectedCheck

================================================
FILE: 3rdlib/commonlog/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.halzhang.android.common.log"
    android:versionCode="1"
    android:versionName="1.0" >

    <application >
    </application>

</manifest>

================================================
FILE: 3rdlib/commonlog/README.md
================================================
CommonLog
=========

Android Log library


================================================
FILE: 3rdlib/commonlog/build.gradle
================================================
buildscript {
    repositories {
        mavenCentral()
    }
}
apply plugin: 'com.android.library'

dependencies {
    compile fileTree(dir:'libs',include:'*.jar')
}

android {
    def globalConfiguration = rootProject.extensions.getByName("ext")

    compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion")
    buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion")

    defaultConfig {
        minSdkVersion globalConfiguration.getAt("androidMinSdkVersion")
        targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion")
    }
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        instrumentTest.setRoot('tests')
    }
}


================================================
FILE: 3rdlib/commonlog/build.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project name="pulltorefresh" default="help">

    <!-- The local.properties file is created and updated by the 'android' tool.
         It contains the path to the SDK. It should *NOT* be checked into
         Version Control Systems. -->
    <property file="local.properties" />

    <!-- The ant.properties file can be created by you. It is only edited by the
         'android' tool to add properties to it.
         This is the place to change some Ant specific build properties.
         Here are some properties you may want to change/update:

         source.dir
             The name of the source directory. Default is 'src'.
         out.dir
             The name of the output directory. Default is 'bin'.

         For other overridable properties, look at the beginning of the rules
         files in the SDK, at tools/ant/build.xml

         Properties related to the SDK location or the project target should
         be updated using the 'android' tool with the 'update' action.

         This file is an integral part of the build system for your
         application and should be checked into Version Control Systems.

         -->
    <property file="ant.properties" />

    <!-- if sdk.dir was not set from one of the property file, then
         get it from the ANDROID_HOME env var.
         This must be done before we load project.properties since
         the proguard config can use sdk.dir -->
    <property environment="env" />
    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
        <isset property="env.ANDROID_HOME" />
    </condition>

    <!-- The project.properties file is created and updated by the 'android'
         tool, as well as ADT.

         This contains project specific properties such as project target, and library
         dependencies. Lower level build properties are stored in ant.properties
         (or in .classpath for Eclipse projects).

         This file is an integral part of the build system for your
         application and should be checked into Version Control Systems. -->
    <loadproperties srcFile="project.properties" />

    <!-- quick check on sdk.dir -->
    <fail
            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
            unless="sdk.dir"
    />

    <!--
        Import per project custom build rules if present at the root of the project.
        This is the place to put custom intermediary targets such as:
            -pre-build
            -pre-compile
            -post-compile (This is typically used for code obfuscation.
                           Compiled code location: ${out.classes.absolute.dir}
                           If this is not done in place, override ${out.dex.input.absolute.dir})
            -post-package
            -post-build
            -pre-clean
    -->
    <import file="custom_rules.xml" optional="true" />

    <!-- Import the actual build file.

         To customize existing targets, there are two options:
         - Customize only one target:
             - copy/paste the target into this file, *before* the
               <import> task.
             - customize it to your needs.
         - Customize the whole content of build.xml
             - copy/paste the content of the rules files (minus the top node)
               into this file, replacing the <import> task.
             - customize to your needs.

         ***********************
         ****** IMPORTANT ******
         ***********************
         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
         in order to avoid having your file be overridden by tools such as "android update project"
    -->
    <!-- version-tag: 1 -->
    <import file="${sdk.dir}/tools/ant/build.xml" />

</project>


================================================
FILE: 3rdlib/commonlog/proguard-project.txt
================================================
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: 3rdlib/commonlog/project.properties
================================================
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

# Project target.
target=android-17
android.library=true


================================================
FILE: 3rdlib/commonlog/src/com/halzhang/android/common/CDLog.java
================================================

package com.halzhang.android.common;

import android.util.Log;

/**
 * CommonLog<br/>
 * log
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version ec 24, 2012 9:55:18 PM
 */
public class CDLog {
    public static final String LOG_TAG = CDLog.class.getSimpleName();

    public static final boolean DEBUG = true;

    private static final String LOG_PREFIX = "CDLOG_";

    private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();

    private static final int MAX_LOG_TAG_LENGTH = 23;

    public static String makeLogTag(String str) {
        if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {
            return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);
        }
        return LOG_PREFIX + str;
    }

    /**
     * WARNING: Don't use this when obfuscating class names with Proguard!
     */
    public static String makeLogTag(Class<?> cls) {
        return makeLogTag(cls.getSimpleName());
    }

    public static void i(String tag, String msg, Throwable throwable) {
        if (DEBUG) {
            Log.i(tag, msg, throwable);
        }
    }

    public static void i(String tag, Object... objects) {
        StringBuilder builder = new StringBuilder();
        for (Object obj : objects) {
            builder.append(obj);
        }
        i(tag, builder.toString());
    }

    public static void i(String tag, String msg) {
        i(tag, msg, null);
    }

    public static void i(String msg) {
        i(LOG_TAG, msg, null);
    }

    public static void d(String tag, String msg, Throwable throwable) {
        if (DEBUG) {
            Log.d(tag, msg, throwable);
        }
    }

    public static void d(String tag, String msg) {
        d(tag, msg, null);
    }

    public static void d(String msg) {
        d(LOG_TAG, msg, null);
    }

    public static void w(String tag, String msg, Throwable throwable) {
        if (DEBUG) {
            Log.w(tag, msg, throwable);
        }
    }

    public static void w(String tag, String msg) {
        w(tag, msg, null);
    }

    public static void w(String msg) {
        w(LOG_TAG, msg, null);
    }

    public static void e(String tag, String msg, Throwable throwable) {
        if (DEBUG) {
            Log.e(tag, msg, throwable);
        }
    }

    public static void e(String tag, String msg) {
        e(tag, msg, null);
    }

    public static void e(String msg) {
        e(LOG_TAG, msg, null);
    }

    private CDLog() {
    }
}


================================================
FILE: 3rdlib/commontoast/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.halzhang.android.common.toast"
    android:versionCode="1"
    android:versionName="1.0" >

    <applicatio>
    </applicatio>

</manifest>

================================================
FILE: 3rdlib/commontoast/README.md
================================================
CommonToast
===========

Android Toast library


================================================
FILE: 3rdlib/commontoast/build.gradle
================================================
buildscript {
    repositories {
        mavenCentral()
    }
}
apply plugin: 'com.android.library'

dependencies {
    compile fileTree(dir:'libs',include:'*.jar')
}

android {
    def globalConfiguration = rootProject.extensions.getByName("ext")

    compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion")
    buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion")

    defaultConfig {
        minSdkVersion globalConfiguration.getAt("androidMinSdkVersion")
        targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion")
    }
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        instrumentTest.setRoot('tests')
    }
}


================================================
FILE: 3rdlib/commontoast/build.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project name="pulltorefresh" default="help">

    <!-- The local.properties file is created and updated by the 'android' tool.
         It contains the path to the SDK. It should *NOT* be checked into
         Version Control Systems. -->
    <property file="local.properties" />

    <!-- The ant.properties file can be created by you. It is only edited by the
         'android' tool to add properties to it.
         This is the place to change some Ant specific build properties.
         Here are some properties you may want to change/update:

         source.dir
             The name of the source directory. Default is 'src'.
         out.dir
             The name of the output directory. Default is 'bin'.

         For other overridable properties, look at the beginning of the rules
         files in the SDK, at tools/ant/build.xml

         Properties related to the SDK location or the project target should
         be updated using the 'android' tool with the 'update' action.

         This file is an integral part of the build system for your
         application and should be checked into Version Control Systems.

         -->
    <property file="ant.properties" />

    <!-- if sdk.dir was not set from one of the property file, then
         get it from the ANDROID_HOME env var.
         This must be done before we load project.properties since
         the proguard config can use sdk.dir -->
    <property environment="env" />
    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
        <isset property="env.ANDROID_HOME" />
    </condition>

    <!-- The project.properties file is created and updated by the 'android'
         tool, as well as ADT.

         This contains project specific properties such as project target, and library
         dependencies. Lower level build properties are stored in ant.properties
         (or in .classpath for Eclipse projects).

         This file is an integral part of the build system for your
         application and should be checked into Version Control Systems. -->
    <loadproperties srcFile="project.properties" />

    <!-- quick check on sdk.dir -->
    <fail
            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
            unless="sdk.dir"
    />

    <!--
        Import per project custom build rules if present at the root of the project.
        This is the place to put custom intermediary targets such as:
            -pre-build
            -pre-compile
            -post-compile (This is typically used for code obfuscation.
                           Compiled code location: ${out.classes.absolute.dir}
                           If this is not done in place, override ${out.dex.input.absolute.dir})
            -post-package
            -post-build
            -pre-clean
    -->
    <import file="custom_rules.xml" optional="true" />

    <!-- Import the actual build file.

         To customize existing targets, there are two options:
         - Customize only one target:
             - copy/paste the target into this file, *before* the
               <import> task.
             - customize it to your needs.
         - Customize the whole content of build.xml
             - copy/paste the content of the rules files (minus the top node)
               into this file, replacing the <import> task.
             - customize to your needs.

         ***********************
         ****** IMPORTANT ******
         ***********************
         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
         in order to avoid having your file be overridden by tools such as "android update project"
    -->
    <!-- version-tag: 1 -->
    <import file="${sdk.dir}/tools/ant/build.xml" />

</project>


================================================
FILE: 3rdlib/commontoast/proguard-project.txt
================================================
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


================================================
FILE: 3rdlib/commontoast/project.properties
================================================
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

# Project target.
target=android-17
android.library=true


================================================
FILE: 3rdlib/commontoast/src/com/halzhang/android/common/CDToast.java
================================================

package com.halzhang.android.common;

import android.content.Context;
import android.os.Handler;
import android.widget.Toast;

/**
 * CommonToast<br/>
 * Toast
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Dec 27, 2012 11:39:39 AM
 */
public class CDToast {

    private static Toast sToast;

    private static Handler mHandler;

    /**
     * @param context
     * @param text
     */
    public static void showToast(Context context, String text) {
        if (sToast == null) {
            mHandler = new Handler(context.getMainLooper());
            sToast = Toast.makeText(context.getApplicationContext(), text, Toast.LENGTH_SHORT);
        } else {
            sToast.setText(text);
        }
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                sToast.show();
            }
        });
    }

    /**
     * @param context
     * @param resId
     */
    public static void showToast(Context context, int resId) {
        String text = context.getString(resId);
        showToast(context, text);
    }

}


================================================
FILE: README.md
================================================
# [Startup News](http://news.dbanotes.net) Android App

[![Build Status](https://travis-ci.org/halzhang/StartupNews.svg?branch=master)](https://travis-ci.org/halzhang/StartupNews)

[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=com.halzhang.android.apps.startupnews) 
![image](https://raw.github.com/halzhang/StartupNews/master/QR.jpg)
[本地下载](https://github.com/halzhang/StartupNews/raw/master/release/StartupNews-release.apk)
支持系统
---
Android 4.0+

反馈
---
邮箱:[ghanguo@gmail.com](mailto:ghanguo@gmail.com)

欢迎直接在[issues](https://github.com/halzhang/StartupNews/issues/new)提bug;

作者
---
[HalZhang](http://weibo.com/halzhang)

感谢  [@Fenng](http://www.weibo.com/fenng) 创建了Startup News.

Changelog
---
[changelog](https://github.com/halzhang/StartupNews/wiki/Changelog)

License
--------

    Copyright 2016 Zhang Hanguo

    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.


================================================
FILE: android-wait-for-emulator
================================================
#!/bin/bash

# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain

set +e

bootanim=""
failcounter=0
timeout_in_sec=360

until [[ "$bootanim" =~ "stopped" ]]; do
  bootanim=`adb -e shell getprop init.svc.bootanim 2>&1 &`
  if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline"
    || "$bootanim" =~ "running" ]]; then
    let "failcounter += 1"
    echo "Waiting for emulator to start"
    if [[ $failcounter -gt timeout_in_sec ]]; then
      echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator"
      exit 1
    fi
  fi
  sleep 1
done

echo "Emulator is ready"

================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    def globalConfiguration = rootProject.extensions.getByName("ext")

    def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()

    compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion")
    buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion")

    defaultConfig {
        minSdkVersion globalConfiguration.getAt("androidMinSdkVersion")
        targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion")
        applicationId "com.halzhang.android.apps.startupnews"
        versionCode 13
        versionName "2.0.0-${gitSha}"
    }

    signingConfigs {
        release
    }

    File storeFile = file("../local.properties")
    if (storeFile.canRead()) {
        Properties p = new Properties()
        p.load(storeFile.newDataInputStream())
        if (p != null && p.containsKey('key.store') && p.containsKey("key.store.password")
                && p.containsKey("key.alias") && p.containsKey("key.alias.password")) {
            android.signingConfigs.release.storeFile = file(p.getProperty("key.store"))
            android.signingConfigs.release.storePassword = p.getProperty("key.store.password")
            android.signingConfigs.release.keyAlias = p.getProperty("key.alias")
            android.signingConfigs.release.keyPassword = p.getProperty("key.alias.password")
        } else {
            android.buildTypes.release.signingConfig = null
        }
    } else {
        android.buildTypes.release.signingConfig = null
    }

    buildTypes {
        debug {
            debuggable true
            versionNameSuffix "-debug"
            applicationIdSuffix ".debug"
            resValue "string", "app_name", "SN(debug)"
        }

        release {

            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    if (output.outputFile != null && output.outputFile.name.endsWith('.apk')
                            && 'release'.equals(variant.buildType.name)) {
                        def apkFile = new File(
                                output.outputFile.getParent(),
                                "StartupNews_${variant.flavorName}_v${variant.versionName}.apk")
                        output.outputFile = apkFile
                    }
                }
            }


            resValue "string", "app_name", "StartupNews"
            shrinkResources true
            debuggable false
            signingConfig signingConfigs.release
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
        }

    }

    productFlavors {
        googleplay {}
    }

}

dependencies {

    def androidSupportVersion = rootProject.ext.androidSupportVersion
    def mvpVersion = rootProject.ext.mvpVersion

    compile fileTree(dir: 'libs', include: '*.jar')
    compile "com.android.support:appcompat-v7:${androidSupportVersion}"
    compile "com.android.support:support-v4:${androidSupportVersion}"
    compile "com.android.support:support-annotations:${androidSupportVersion}"
    compile "com.android.support:recyclerview-v7:${androidSupportVersion}"
    compile "com.android.support:cardview-v7:${androidSupportVersion}"
    compile "com.android.support:design:${androidSupportVersion}"
    compile "com.android.support:customtabs:${androidSupportVersion}"
    compile "com.github.halzhang:mvp:${mvpVersion}"
    compile "com.github.halzhang:mvp-support-v4:${mvpVersion}"
    compile "com.github.halzhang:mvp-support-v7:${mvpVersion}"
    // Dagger dependencies
    apt "com.google.dagger:dagger-compiler:$rootProject.daggerVersion"
    provided 'org.glassfish:javax.annotation:10.0-b28'
    compile "com.google.dagger:dagger:$rootProject.daggerVersion"

    compile project(':3rdlib:commonlog')
    compile project(':3rdlib:commontoast')
    compile project(':data')
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
    compile 'org.jsoup:jsoup:1.7.2'
    compile 'com.android.support:support-v4:23.4.0'
}


================================================
FILE: app/proguard-project.txt
================================================
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

-keep class org.jsoup.** { *; }
-keep interface org.jsoup.** { *; }


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.halzhang.android.apps.startupnews">

    <supports-screens
        android:anyDensity="true"
        android:xlargeScreens="true" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- Gmail permission -->
    <uses-permission android:name="com.google.android.gm.permission.AUTO_SEND" />

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:description="@string/app_description"
        android:icon="@drawable/ic_launcher">

        <activity
            android:name=".ui.MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme"
            android:configChanges="uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.phone.BrowseActivity"
            android:theme="@style/AppPreferenceTheme"
            android:configChanges="uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize" />
        <activity
            android:name=".ui.DiscussActivity"
            android:theme="@style/AppTheme"
            android:configChanges="uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize"
            android:label="@string/title_discuss" />
        <!--
             <activity
            android:name=".ui.SubmitActivity"
            android:configChanges="uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale"
            android:label="@string/title_shareto_startupnews"
            android:screenOrientation="portrait">
            <intent-filter >
                <action android:name="android.intent.action.SEND"/>
                <data android:mimeType="text/plain"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>

        </activity>
        -->
        <activity
            android:name=".ui.AboutActivity"
            android:theme="@style/AppPreferenceTheme"
            android:configChanges="uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize"
            android:label="@string/title_settings" />
        <activity
            android:name=".ui.LoginActivity"
            android:theme="@style/AppPreferenceTheme"
            android:configChanges="uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize"
            android:label="@string/title_login"
            android:windowSoftInputMode="stateHidden" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ApplicationModule.java
================================================
package com.halzhang.android.apps.startupnews;

import android.content.Context;

import com.halzhang.android.startupnews.data.utils.OkHttpClientHelper;

import dagger.Module;
import dagger.Provides;

/**
 * Created by Hal on 2016/5/30.
 */
@Module
public class ApplicationModule {

    private final Context mContext;


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

    @Provides
    Context provideContext() {
        return mContext;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/Constants.java
================================================

package com.halzhang.android.apps.startupnews;

/**
 * Constants used by StartupNews application
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Jan 16, 2013 11:46:03 AM
 */
public abstract class Constants {

    /**
     * abs class
     */
    protected Constants() {

    }

    /**
     * Root Dir used by SN app
     */
    public static final String SDCARD_TOP_DIR = "/SN/";

    /**
     * Crash log dir
     */
    public static final String CRASH_LOG_DIR = SDCARD_TOP_DIR+"/crash/";

    /**
     * 历史记录保存文件名
     */
    public static final String HISTORY_FILE_NAME = "history.db";

    public final class IntentAction {

        /**
         * 登陆后结果cookie user
         * 
         * @see #ACTION_LOGIN
         */
        public static final String EXTRA_LOGIN_USER = "startupnews.intent.extra.login.USER";

        /**
         * 登陆结果
         * <p>
         * 输出:登陆后的cookie extras:LOGIN_USER(value:String)
         * </p>
         * 
         * @see #EXTRA_LOGIN_USER
         */
        public static final String ACTION_LOGIN = "startupnews.intent.action.LOGIN";
        
        /**
         * 注销
         */
        public static final String ACTION_LOGOUT = "startupnews.intent.action.LOGOUT";
        
    }
    
    public static final String TAG_BROWSE_FRAGMENT = "tag_browse_fragment";

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/MyApplication.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews;

import android.app.Application;
import android.os.StrictMode;
import android.text.TextUtils;

import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.utils.CrashHandler;
import com.halzhang.android.common.CDLog;
import com.halzhang.android.startupnews.data.CookieFactoryModule;
import com.halzhang.android.startupnews.data.OkHttpClientModule;
import com.halzhang.android.startupnews.data.SnApiModule;
import com.squareup.leakcanary.LeakCanary;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * StartupNews
 * <p>
 * app
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 8, 2013
 */
public class MyApplication extends Application {

    private static final String LOG_TAG = MyApplication.class.getSimpleName();

    private HashSet<String> mHistorySet = new HashSet<String>();

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "StartupNews thread #" + mCount.getAndIncrement());
        }
    };

    private ExecutorService mExecutorService;

    private static MyApplication me;

    public static MyApplication instance() {
        return me;
    }

    public MyApplication() {
        super();
        me = this;
    }

    private SnApiComponent mSnApiComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        Tracker.getInstance().init(this);
        CrashHandler.getInstance().init(this);
        mExecutorService = Executors.newSingleThreadExecutor(sThreadFactory);
        initHistory();
        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(new StrictMode
                    .ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyLog()
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectActivityLeaks()
                    .detectLeakedClosableObjects()
                    .penaltyLog()
                    .build());
            LeakCanary.install(this);
        }

        mSnApiComponent = DaggerSnApiComponent.builder().applicationModule(new ApplicationModule(this))
                .okHttpClientModule(new OkHttpClientModule())
                .snApiModule(new SnApiModule())
                .cookieFactoryModule(new CookieFactoryModule()).build();
        mSnApiComponent.getSessionManager().initSession(this);
    }

    public SnApiComponent getSnApiComponent() {
        return mSnApiComponent;
    }

    private void initHistory() {
        File file = new File(getFilesDir().getAbsolutePath() + File.separator
                + Constants.HISTORY_FILE_NAME);
        if (!file.exists()) {
            return;
        }
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(file));
            String s = null;
            while (!TextUtils.isEmpty((s = reader.readLine()))) {
                mHistorySet.add(s);
            }
        } catch (FileNotFoundException e) {
            CDLog.e(LOG_TAG, "", e);
            Tracker.getInstance().sendException("History file not found!", e, false);
        } catch (IOException e) {
            CDLog.e(LOG_TAG, "", e);
            Tracker.getInstance().sendException("Read History file error!", e, false);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private void storeHistory() {
        if (!mHistorySet.isEmpty()) {
            Iterator<String> iterator = mHistorySet.iterator();
            StringBuilder builder = new StringBuilder();
            while (iterator.hasNext()) {
                builder.append(iterator.next()).append("\n");
            }
            File file = new File(getFilesDir().getAbsolutePath() + File.separator
                    + Constants.HISTORY_FILE_NAME);
            if (!file.exists()) {
                try {
                    file.createNewFile();
                } catch (IOException e) {
                    CDLog.e(LOG_TAG, "Create history file error!");
                    Tracker.getInstance().sendException("Create history file error!", e, false);
                }
            }
            PrintWriter writer = null;
            try {
                writer = new PrintWriter(file);
                writer.write(builder.toString());
                writer.close();
            } catch (FileNotFoundException e) {
                CDLog.e(LOG_TAG, "History file not found!");
                Tracker.getInstance().sendException("History file not found!", e, false);
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        }
    }

    /**
     * 增加访问历史记录
     *
     * @param url
     */
    public void addHistory(String url) {
        if (!TextUtils.isEmpty(url) && !mHistorySet.contains(url)) {
            mHistorySet.add(url);
            mExecutorService.submit(new Runnable() {

                @Override
                public void run() {
                    storeHistory();
                }
            });
        }
    }

    /**
     * 清空历史记录
     */
    public void clearHistory() {
        mHistorySet.clear();
    }

    public boolean isHistoryContains(String url) {
        return mHistorySet.contains(url);
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/SnApiComponent.java
================================================
package com.halzhang.android.apps.startupnews;

import com.halzhang.android.startupnews.data.CookieFactoryModule;
import com.halzhang.android.startupnews.data.JsoupConnectorModule;
import com.halzhang.android.startupnews.data.OkHttpClientModule;
import com.halzhang.android.startupnews.data.SessionManagerModule;
import com.halzhang.android.startupnews.data.net.ISnApi;
import com.halzhang.android.startupnews.data.SnApiModule;
import com.halzhang.android.startupnews.data.net.JsoupConnector;
import com.halzhang.android.startupnews.data.utils.SessionManager;

import javax.inject.Singleton;

import dagger.Component;

/**
 * Created by Hal on 2016/5/30.
 */
@Singleton
@Component(modules = {ApplicationModule.class, SnApiModule.class, OkHttpClientModule.class,
        SessionManagerModule.class, JsoupConnectorModule.class, CookieFactoryModule.class})
public interface SnApiComponent {

    ISnApi getSnApi();

    SessionManager getSessionManager();

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/analytics/MyExceptionParser.java
================================================
/*
 * Copyright (C) 2013 HalZhang.
 *
 * http://www.gnu.org/licenses/gpl-3.0.txt
 */

package com.halzhang.android.apps.startupnews.analytics;

import com.google.analytics.tracking.android.ExceptionParser;
import com.halzhang.android.apps.startupnews.utils.AppUtils;

import android.content.Context;
import android.os.Build;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

/**
 * StartupNews
 * <p>
 * 异常解析
 * </p>
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 26, 2013
 */
public class MyExceptionParser implements ExceptionParser {

    private Context mContext;

    private Map<String, String> infos = new HashMap<String, String>();

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

    @Override
    public String getDescription(String message, Throwable throwable) {
        collectDeviceInfo(mContext);
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key).append("=").append(value).append("\n");
        }
        sb.append(getStackTraceString(throwable));
        return sb.toString();
    }

    public static String getStackTraceString(Throwable tr) {
        if (tr == null) {
            return "";
        }
        Throwable t = tr;
        while (t != null) {
            if (t instanceof UnknownHostException) {
                return "";
            }
            t = t.getCause();
        }
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        tr.printStackTrace(pw);
        return sw.toString();
    }

    public void collectDeviceInfo(Context ctx) {
        infos.clear();
        infos.put("versionName", AppUtils.getVersionName(mContext));
        infos.put("versionCode", String.valueOf(AppUtils.getVersionCode(mContext)));
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
            }
        }
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/analytics/Tracker.java
================================================
package com.halzhang.android.apps.startupnews.analytics;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import com.google.analytics.tracking.android.EasyTracker;
import com.google.analytics.tracking.android.MapBuilder;

/**
 * 统计
 * Created by Hal on 15/5/6.
 */
public class Tracker {

    private static final String LOG_TAG = "Tracker";

    private EasyTracker mEasyTracker;

    private Tracker() {

    }

    private static class InstanceHolder {
        private static final Tracker INSTANCE = new Tracker();
    }

    public static Tracker getInstance() {
        return InstanceHolder.INSTANCE;
    }

    public void init(Context context) {
        mEasyTracker = EasyTracker.getInstance(context);
    }

    public void sendException(String message, Throwable e, boolean fatal) {
        if(mEasyTracker == null){
            Log.w(LOG_TAG,"Tracker has not init!");
            return;
        }
        mEasyTracker.send(MapBuilder.createException(TextUtils.isEmpty(message) ? (e == null ? "" : e.getMessage()) : message, fatal).build());
    }

    public void sendEvent(String category, String action, String label, Long value) {
        if(mEasyTracker == null){
            Log.w(LOG_TAG,"Tracker has not init!");
            return;
        }
        mEasyTracker.send(MapBuilder.createEvent(category, action, label, value).build());
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BasePresenter.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

/**
 * Created by Hal on 16/6/11.
 */
public interface BasePresenter {

    void start();
    void stop();

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BaseView.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

/**
 * Created by Hal on 16/6/11.
 */
public interface BaseView<T> {

    void setPresenter(T presenter);

    boolean isActive();

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListContract.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.startupnews.data.entity.SNComment;

import java.util.ArrayList;

/**
 * Created by Hal on 16/8/14.
 */
public interface CommentsListContract {

    interface Presenter extends BasePresenter {
        void getComments(String url);

        void getMoreComments();
    }

    interface View extends BaseView<Presenter> {
        void onSuccess(ArrayList<SNComment> snComments);

        void onFailure(Throwable e);

        void onAtEnd();
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenter.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.halzhang.android.startupnews.data.entity.SNComments;
import com.halzhang.android.startupnews.data.net.ISnApi;

import javax.inject.Inject;

import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * Created by Hal on 16/8/14.
 */
public class CommentsListPresenter implements CommentsListContract.Presenter {

    @NonNull
    private final ISnApi mSnApi;
    @NonNull
    private final CommentsListContract.View mView;

    private SNComments mComments;

    @Inject
    public CommentsListPresenter(@NonNull ISnApi snApi, @NonNull CommentsListContract.View view) {
        mSnApi = snApi;
        mView = view;
    }

    @Inject
    void setupListener() {
        mView.setPresenter(this);
    }

    @Override
    public void getComments(String url) {
        mSnApi.getSNComments(url)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<SNComments>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        if (mView.isActive()) {
                            mView.onFailure(e);
                        }
                    }

                    @Override
                    public void onNext(SNComments snComments) {
                        if (snComments != null) {
                            mComments = snComments;
                            if (mView.isActive()) {
                                mView.onSuccess(mComments.getSnComments());
                            }
                        }
                    }
                });
    }

    @Override
    public void getMoreComments() {
        if (mComments == null || TextUtils.isEmpty(mComments.getMoreURL())) {
            if (mView.isActive()) {
                mView.onAtEnd();
            }
        } else {
            mSnApi.getSNComments(mComments.getMoreURL())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Subscriber<SNComments>() {
                        @Override
                        public void onCompleted() {

                        }

                        @Override
                        public void onError(Throwable e) {
                            if (mView.isActive()) {
                                mView.onFailure(e);
                            }
                        }

                        @Override
                        public void onNext(SNComments snComments) {
                            if (snComments != null) {
                                if (mComments == null) {
                                    mComments = snComments;
                                } else {
                                    mComments.addComments(snComments.getSnComments());
                                    mComments.setMoreURL(snComments.getMoreURL());
                                }
                                if (mView.isActive()) {
                                    mView.onSuccess(mComments.getSnComments());
                                }
                            }
                        }
                    });
        }
    }

    /* no-op */
    @Override
    public void start() {
    }

    @Override
    public void stop() {

    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenterModule.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import dagger.Module;
import dagger.Provides;

/**
 * Module for Comments provide Comments view
 * Created by Hal on 16/6/12.
 */
@Module
public class CommentsListPresenterModule {

    private final CommentsListContract.View mView;

    public CommentsListPresenterModule(CommentsListContract.View view) {
        mView = view;
    }

    @Provides
    CommentsListContract.View provideCommentsListContractView() {
        return mView;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussComponent.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.ui.DiscussActivity;
import com.halzhang.android.apps.startupnews.utils.ActivityScoped;

import dagger.Component;

/**
 * Created by Hal on 16/8/14.
 */
@ActivityScoped
@Component(dependencies = SnApiComponent.class, modules = DiscussPresenterModule.class)
public interface DiscussComponent {

    void inject(DiscussActivity discussActivity);

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussContract.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.Status;

/**
 * Created by Hal on 16/8/14.
 */
public interface DiscussContract {

    interface Presenter extends BasePresenter {
        void getDiscuss(String url);

        void comment(String message);
    }

    interface View extends BaseView<Presenter> {
        void onGetDiscuss(SNDiscuss snDiscuss);

        void onGetDiscussFailure(Throwable e);

        void onCommentSuccess(Status status);

        void onCommentFailure(Throwable e);

        void onSessionExpired();
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenter.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import android.support.annotation.NonNull;

import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.Status;
import com.halzhang.android.startupnews.data.net.ISnApi;
import com.halzhang.android.startupnews.data.utils.SessionManager;

import javax.inject.Inject;

import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * Created by Hal on 16/8/14.
 */
public class DiscussPresenter implements DiscussContract.Presenter {

    @NonNull
    private final DiscussContract.View mView;

    @NonNull
    private final ISnApi mSnApi;

    @NonNull
    private final SessionManager mSessionManager;

    private String mFnid;

    @Inject
    public DiscussPresenter(DiscussContract.View view, ISnApi snApi, SessionManager sessionManager) {
        mView = view;
        mSnApi = snApi;
        mSessionManager = sessionManager;
    }

    @Inject
    void setupListener() {
        mView.setPresenter(this);
    }

    @Override
    public void start() {

    }

    @Override
    public void stop() {

    }

    @Override
    public void getDiscuss(String url) {
        mSnApi.getDiscuss(url)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<SNDiscuss>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        if (mView.isActive()) {
                            mView.onGetDiscussFailure(e);
                        }
                    }

                    @Override
                    public void onNext(SNDiscuss snDiscuss) {
                        if (mView.isActive()) {
                            if (snDiscuss != null) {
                                mFnid = snDiscuss.getFnid();
                            }
                            mView.onGetDiscuss(snDiscuss);
                        }
                    }
                });
    }

    @Override
    public void comment(String message) {
        if (!mSessionManager.isValid()) {
            if (mView.isActive()) {
                mView.onSessionExpired();
            }
            return;
        }
        mSnApi.comment(message, mFnid)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Status>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        if (mView.isActive()) {
                            mView.onCommentFailure(e);
                        }
                    }

                    @Override
                    public void onNext(Status status) {
                        if (mView.isActive()) {
                            mView.onCommentSuccess(status);
                        }
                    }
                });
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenterModule.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import dagger.Module;
import dagger.Provides;

/**
 * Created by Hal on 16/8/14.
 */
@Module
public class DiscussPresenterModule {

    private DiscussContract.View mView;

    public DiscussPresenterModule(DiscussContract.View view) {
        mView = view;
    }

    @Provides
    public DiscussContract.View provideDiscussContractView() {
        return mView;
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginComponent.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.ui.LoginActivity;
import com.halzhang.android.apps.startupnews.utils.ActivityScoped;
import com.halzhang.android.apps.startupnews.utils.FragmentScoped;

import dagger.Component;

/**
 * Dagger component for login
 * Created by Hal on 16/6/12.
 */
@ActivityScoped
@Component(dependencies = SnApiComponent.class, modules = LoginPresenterModule.class)
public interface LoginComponent {

    void inject(LoginActivity loginActivity);

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginContract.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import rx.Subscription;

/**
 * Contract for login presenter and view
 * Created by Hal on 16/6/12.
 */
public interface LoginContract {

    interface Presenter extends BasePresenter {
        void login(String username, String password);
    }

    interface View extends BaseView<Presenter> {

        void onLoginError(Throwable e);

        void onLoginResult(String user);

        void addSubscription(Subscription subscription);
    }


}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenter.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import android.support.annotation.NonNull;

import com.halzhang.android.startupnews.data.net.ISnApi;

import javax.inject.Inject;

import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

/**
 * Presenter for Login
 * Created by Hal on 16/6/12.
 */
public class LoginPresenter implements LoginContract.Presenter {

    @NonNull
    private final LoginContract.View mView;

    @NonNull
    private final ISnApi mSnApi;

    @Inject
    public LoginPresenter(@NonNull LoginContract.View view, @NonNull ISnApi snApi) {
        mView = view;
        mSnApi = snApi;
    }

    @Inject
    void setupListener() {
        mView.setPresenter(this);
    }

    @Override
    public void login(final String username, final String password) {
        mView.addSubscription(
                mSnApi.getFnid()
                        .flatMap(new Func1<String, Observable<String>>() {
                            @Override
                            public Observable<String> call(String s) {
                                return mSnApi.login(s, username, password);
                            }
                        })
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(new Subscriber<String>() {
                            @Override
                            public void onCompleted() {

                            }

                            @Override
                            public void onError(Throwable e) {
                                if (mView.isActive()) {
                                    mView.onLoginError(e);
                                }
                            }

                            @Override
                            public void onNext(String s) {
                                if (mView.isActive()) {
                                    mView.onLoginResult(s);
                                }
                            }
                        })
        );
    }

    /* no-op */
    @Override
    public void start() {

    }

    @Override
    public void stop() {

    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenterModule.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import dagger.Module;
import dagger.Provides;

/**
 * Module for login provide login view
 * Created by Hal on 16/6/12.
 */
@Module
public class LoginPresenterModule {

    private final LoginContract.View mView;

    public LoginPresenterModule(LoginContract.View view) {
        mView = view;
    }

    @Provides
    LoginContract.View provideLoginContractView() {
        return mView;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityContract.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.startupnews.data.entity.Status;

/**
 * Created by Hal on 16/8/23.
 */
public interface MainActivityContract {

    interface Presenter extends BasePresenter {
        void logout();

        /**
         * 投票,顶!d=====( ̄▽ ̄*)b
         *
         * @param postId
         */
        void upVote(String postId);
    }

    interface View extends BaseView<Presenter> {

        /**
         * 注销登录
         *
         * @param result true 成功
         */
        void onLogoutResult(boolean result);

        void onUpVoteFailure(Throwable e);

        void onUpVoteSuccess(Status status);

    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenter.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import android.support.annotation.NonNull;

import com.halzhang.android.startupnews.data.entity.Status;
import com.halzhang.android.startupnews.data.net.ISnApi;

import javax.inject.Inject;

import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * Created by Hal on 16/8/23.
 */
public class MainActivityPresenter implements MainActivityContract.Presenter {

    @NonNull
    private final ISnApi mSnApi;
    @NonNull
    private final MainActivityContract.View mView;

    @Inject
    public MainActivityPresenter(ISnApi snApi, MainActivityContract.View view) {
        mSnApi = snApi;
        mView = view;
    }

    @Inject
    void setupListener() {
        mView.setPresenter(this);
    }

    @Override
    public void logout() {
        mSnApi.logout().subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Boolean>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Boolean aBoolean) {
                        if (mView.isActive()) {
                            mView.onLogoutResult(aBoolean);
                        }
                    }
                });
    }

    @Override
    public void upVote(String postId) {
        mSnApi.upVote(postId)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Status>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        if (mView.isActive()) {
                            mView.onUpVoteFailure(e);
                        }
                    }

                    @Override
                    public void onNext(Status status) {
                        if (mView.isActive()) {
                            mView.onUpVoteSuccess(status);
                        }
                    }
                });
    }

    @Override
    public void start() {

    }

    @Override
    public void stop() {

    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenterModule.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import dagger.Module;
import dagger.Provides;

/**
 * Created by Hal on 16/8/23.
 */
@Module
public class MainActivityPresenterModule {

    private MainActivityContract.View mView;

    public MainActivityPresenterModule(MainActivityContract.View view) {
        mView = view;
    }

    @Provides
    public MainActivityContract.View provideMainActivityContractView() {
        return mView;
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainComponent.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.ui.MainActivity;
import com.halzhang.android.apps.startupnews.utils.ActivityScoped;

import dagger.Component;

/**
 * 主页
 * Created by Hal on 16/8/14.
 */
@ActivityScoped
@Component(dependencies = SnApiComponent.class, modules = {CommentsListPresenterModule.class,
        MainActivityPresenterModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListContract.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.startupnews.data.entity.SNNew;

import java.util.ArrayList;

/**
 * Created by Hal on 16/8/14.
 */
public interface NewsListContract {

    interface Presenter extends BasePresenter {
        void getFeed(String url);

        void getMoreFeed();
    }

    interface View extends BaseView<NewsListContract.Presenter> {
        void onSuccess(ArrayList<SNNew> snNews);

        void onFailure(Throwable e);

        void onAtEnd();
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListFragmentComponent.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.ui.fragment.NewsListFragment;
import com.halzhang.android.apps.startupnews.utils.FragmentScoped;

import dagger.Component;

/**
 * Created by Hal on 16/8/14.
 */
@FragmentScoped
@Component(dependencies = SnApiComponent.class, modules = NewsListPresenterModule.class)
public interface NewsListFragmentComponent {

    void inject(NewsListFragment newsListFragment);

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenter.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.halzhang.android.startupnews.data.entity.SNFeed;
import com.halzhang.android.startupnews.data.net.ISnApi;

import javax.inject.Inject;

import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * 最新文章列表
 * Created by Hal on 2016/5/12.
 */
public class NewsListPresenter implements NewsListContract.Presenter {

    @NonNull
    private final ISnApi mSnApi;

    @NonNull
    private final NewsListContract.View mView;

    private SNFeed mSNFeed;

    @Inject
    public NewsListPresenter(ISnApi snApi, NewsListContract.View view) {
        mSnApi = snApi;
        mView = view;
    }

    @Inject
    void setupListener() {
        mView.setPresenter(this);
    }

    @Override
    public void start() {

    }

    @Override
    public void stop() {

    }

    @Override
    public void getFeed(String url) {
        mSnApi.getSNFeed(url)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<SNFeed>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        if (mView.isActive()) {
                            mView.onFailure(e);
                        }
                    }

                    @Override
                    public void onNext(SNFeed snFeed) {
                        if (snFeed != null) {
                            mSNFeed = snFeed;
                            if (mView.isActive()) {
                                mView.onSuccess(mSNFeed.getSnNews());
                            }
                        }
                    }
                });
    }

    @Override
    public void getMoreFeed() {
        if (mSNFeed != null && !TextUtils.isEmpty(mSNFeed.getMoreUrl())) {
            mSnApi.getSNFeed(mSNFeed.getMoreUrl())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Subscriber<SNFeed>() {
                        @Override
                        public void onCompleted() {

                        }

                        @Override
                        public void onError(Throwable e) {
                            if (mView.isActive()) {
                                mView.onFailure(e);
                            }
                        }

                        @Override
                        public void onNext(SNFeed snFeed) {
                            if (snFeed != null) {
                                if (mSNFeed == null) {
                                    mSNFeed = snFeed;
                                } else {
                                    mSNFeed.setMoreUrl(snFeed.getMoreUrl());
                                    mSNFeed.addNews(snFeed.getSnNews());
                                }
                                if (mView.isActive()) {
                                    mView.onSuccess(mSNFeed.getSnNews());
                                }
                            }
                        }
                    });
        } else {
            if (mView.isActive()) {
                mView.onAtEnd();
            }
        }
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenterModule.java
================================================
package com.halzhang.android.apps.startupnews.presenter;

import dagger.Module;
import dagger.Provides;

/**
 * Module for News List provide newt view
 * Created by Hal on 16/6/12.
 */
@Module
public class NewsListPresenterModule {

    private final NewsListContract.View mView;

    public NewsListPresenterModule(NewsListContract.View view) {
        mView = view;
    }

    @Provides
    NewsListContract.View provideNewsListContractView() {
        return mView;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/AboutActivity.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.ui;

import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceFragment;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;

import com.google.analytics.tracking.android.EasyTracker;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.utils.AppUtils;

/**
 * StartupNews
 * <p>
 * 设置
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 8, 2013
 */
public class AboutActivity extends BaseFragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
        getFragmentManager().beginTransaction().replace(android.R.id.content, new AppPreferenceFragment()).commit();

    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
//        LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
//        Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.preference_toolbar, root, false);
//        root.addView(bar, 0); // insert at top
//        bar.setTitleTextColor(getResources().getColor(android.R.color.white));
//        bar.setNavigationOnClickListener(new View.OnClickListener() {
//            @Override
//            public void onClick(View v) {
//                finish();
//            }
//        });
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                break;

            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    public static class AppPreferenceFragment extends PreferenceFragment implements OnPreferenceChangeListener {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.preferences);
            ListPreference listPreference = (ListPreference) findPreference(getString(R.string.pref_key_html_provider));
            listPreference.setOnPreferenceChangeListener(this);
            listPreference.setSummary(listPreference.getEntry());
            (findPreference(getString(R.string.pref_key_default_browse)))
                    .setOnPreferenceChangeListener(this);
            Preference versionPref = findPreference(getString(R.string.pref_key_version));
            versionPref.setSummary(getString(R.string.pref_summary_version,
                    AppUtils.getVersionName(getActivity())));
        }

        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            final String key = preference.getKey();
            if (getString(R.string.pref_key_html_provider).equals(key)) {
                Tracker.getInstance().sendEvent("preference_change_action",
                        "preference_change_html_provider",
                        String.format("html_provider_%1$s", (String) newValue), 0L);
                ListPreference listPreference = (ListPreference) preference;
                preference.setSummary(listPreference.getEntries()[listPreference
                        .findIndexOfValue((String) newValue)]);
            } else if (getString(R.string.pref_key_default_browse).equals(key)) {
                Tracker.getInstance().sendEvent("preference_change_action",
                        "preference_change_default_browse",
                        String.format("default_browse_%1$s", String.valueOf(newValue)), 0L);
            }
            return true;
        }
    }


    @Override
    protected void onStart() {
        super.onStart();
        EasyTracker.getInstance(this).activityStart(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        EasyTracker.getInstance(this).activityStop(this);
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseActivity.java
================================================
package com.halzhang.android.apps.startupnews.ui;

import android.support.v7.app.AppCompatActivity;

/**
 * Created by Hal on 16/6/18.
 */
public class BaseActivity extends AppCompatActivity {
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseFragmentActivity.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */
package com.halzhang.android.apps.startupnews.ui;

import com.google.analytics.tracking.android.EasyTracker;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.mvp.presenter.Presenter;
import com.halzhang.android.mvp.support.v7.MVPAppCompatActivity;

import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Window;
import android.view.WindowManager;

/**
 * StartupNews
 * <p>
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 13, 2013
 */
public class BaseFragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
//            Window w = getWindow();
//            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
//            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        EasyTracker.getInstance(this).activityStart(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        EasyTracker.getInstance(this).activityStop(this);
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/DiscussActivity.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.ui;

import com.halzhang.android.apps.startupnews.Constants.IntentAction;
import com.halzhang.android.apps.startupnews.MyApplication;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.presenter.DaggerDiscussComponent;
import com.halzhang.android.apps.startupnews.presenter.DiscussPresenter;
import com.halzhang.android.apps.startupnews.presenter.DiscussPresenterModule;
import com.halzhang.android.apps.startupnews.ui.tablet.DiscussFragment;
import com.halzhang.android.startupnews.data.entity.SNNew;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;

import javax.inject.Inject;

/**
 * StartupNews
 * <p>
 * 评论界面
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 17, 2013
 */
public class DiscussActivity extends BaseFragmentActivity {

    //private static final String LOG_TAG = DiscussActivity.class.getSimpleName();

    public static final String ARG_DISCUSS_URL = "discuss_url";//required!

    public static final String ARG_SNNEW = "snnew";//Optional

    public static void start(Context context, String discussUrl, SNNew snNew) {
        Intent starter = new Intent(context, DiscussActivity.class);
        starter.putExtra(ARG_DISCUSS_URL, discussUrl);
        starter.putExtra(ARG_SNNEW, snNew);
        context.startActivity(starter);
    }

    @Inject
    DiscussPresenter mDiscussPresenter;

    private DiscussFragment mDiscussFragment;

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(android.content.Context context, Intent intent) {
            final String action = intent.getAction();
            if (IntentAction.ACTION_LOGIN.equals(action)) {
                String user = intent.getStringExtra(IntentAction.EXTRA_LOGIN_USER);
                if (!TextUtils.isEmpty(user)) {
                    mDiscussFragment.loadData();
                }
            }
        }

        ;
    };

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.activity_discuss);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }
        SNNew snNew = (SNNew) getIntent().getSerializableExtra(ARG_SNNEW);
        String mDiscussURL = getIntent().getStringExtra(ARG_DISCUSS_URL);
        if (TextUtils.isEmpty(mDiscussURL)) {
            finish();
        }
        mDiscussFragment = DiscussFragment.newInstance(mDiscussURL,snNew);
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, mDiscussFragment).commitAllowingStateLoss();
        IntentFilter filter = new IntentFilter(IntentAction.ACTION_LOGIN);
        registerReceiver(mReceiver, filter);
        SnApiComponent snApiComponent = ((MyApplication) getApplication()).getSnApiComponent();
        DaggerDiscussComponent.builder().snApiComponent(snApiComponent)
                .discussPresenterModule(new DiscussPresenterModule(mDiscussFragment)).build().inject(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        invalidateOptionsMenu();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }

    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/LauncherActivity.java
================================================
package com.halzhang.android.apps.startupnews.ui;

/**
 * Created by Hal on 15/2/7.
 */
public class LauncherActivity extends BaseFragmentActivity {
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/LoginActivity.java
================================================
/*
 * Copyright (C) 2013 HalZhang.
 *
 * http://www.gnu.org/licenses/gpl-3.0.txt
 */

package com.halzhang.android.apps.startupnews.ui;

import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;

import com.google.analytics.tracking.android.EasyTracker;
import com.google.analytics.tracking.android.MapBuilder;
import com.halzhang.android.apps.startupnews.MyApplication;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.presenter.DaggerLoginComponent;
import com.halzhang.android.apps.startupnews.presenter.LoginPresenter;
import com.halzhang.android.apps.startupnews.presenter.LoginPresenterModule;
import com.halzhang.android.apps.startupnews.ui.fragment.LoginFragment;

import javax.inject.Inject;

/**
 * StartupNews
 * <p>
 * 登陆
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Apr 20, 2013
 */
public class LoginActivity extends BaseActivity {

    private static final String LOG_TAG = LoginActivity.class.getSimpleName();

    @Inject
    LoginPresenter mPresenter;

    private LoginFragment mLoginFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }
        setContentView(R.layout.activity_login);
        mLoginFragment = LoginFragment.newInstance();
        SnApiComponent snApiComponent = ((MyApplication) getApplication()).getSnApiComponent();
        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, mLoginFragment).commit();
        DaggerLoginComponent.builder().loginPresenterModule(new LoginPresenterModule(mLoginFragment))
                .snApiComponent(snApiComponent).build().inject(this);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            case R.id.menu_login:
                EasyTracker.getInstance(this).send(MapBuilder.createEvent("ui_action", "options_item_selected",
                        "loginactivity_menu_login", 0L).build());
                mLoginFragment.attemptLogin();
                return true;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/MainActivity.java
================================================

package com.halzhang.android.apps.startupnews.ui;


import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import com.halzhang.android.apps.startupnews.MyApplication;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.CommentsListPresenter;
import com.halzhang.android.apps.startupnews.presenter.CommentsListPresenterModule;
import com.halzhang.android.apps.startupnews.presenter.DaggerMainComponent;
import com.halzhang.android.apps.startupnews.presenter.MainActivityContract;
import com.halzhang.android.apps.startupnews.presenter.MainActivityPresenter;
import com.halzhang.android.apps.startupnews.presenter.MainActivityPresenterModule;
import com.halzhang.android.apps.startupnews.ui.fragment.CommentsListFragment;
import com.halzhang.android.apps.startupnews.ui.fragment.NewsListFragment;
import com.halzhang.android.apps.startupnews.ui.fragment.NewsListFragment.OnNewsSelectedListener;
import com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;
import com.halzhang.android.apps.startupnews.ui.tablet.BrowseFragment;
import com.halzhang.android.apps.startupnews.ui.tablet.DiscussFragment;
import com.halzhang.android.apps.startupnews.ui.tablet.DiscussFragment.OnMenuSelectedListener;
import com.halzhang.android.apps.startupnews.utils.ActivityUtils;
import com.halzhang.android.apps.startupnews.utils.AppUtils;
import com.halzhang.android.apps.startupnews.utils.CustomTabsActivityHelper;
import com.halzhang.android.common.CDLog;
import com.halzhang.android.common.CDToast;
import com.halzhang.android.startupnews.data.entity.SNNew;
import com.halzhang.android.startupnews.data.entity.Status;
import com.halzhang.android.startupnews.data.utils.SessionManager;

import javax.inject.Inject;

/**
 * 主页
 *
 * @author Hal
 */
public class MainActivity extends BaseFragmentActivity implements OnNewsSelectedListener, OnMenuSelectedListener, MainActivityContract.View {

    private static final String LOG_TAG = MainActivity.class.getSimpleName();

    private static final String TAG_NEWS = "tag_news";

    private static final String TAG_NEWEST = "tag_newest";

    private static final String TAG_COMMENT = "tag_comment";

    private static final String TAG_BROWSE = "tag_browse";

    private static final String TAG_DISCUSS = "tag_discuss";

    private Intent mFeedbackEmailIntent;

    private SNNew mSnNew;
    private CustomTabsActivityHelper mHelper;

    private NewsListFragment mHotNewsListFragment;
    private NewsListFragment mNewsListFragment;
    private CommentsListFragment mCommentsListFragment;

    @Inject
    CommentsListPresenter mCommentsListPresenter;

    @Inject
    MainActivityPresenter mMainActivityPresenter;

    @Inject
    SessionManager mSessionManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        CDLog.i(LOG_TAG, "MainActivity create!");
        super.onCreate(savedInstanceState);
        if (isFinishing()) {
            return;
        }
        setContentView(R.layout.activity_main);
        setupViews();
        mFeedbackEmailIntent = createEmailIntent();
        mHelper = new CustomTabsActivityHelper();

        SnApiComponent snApiComponent = ((MyApplication) getApplication()).getSnApiComponent();
        DaggerMainComponent.builder().snApiComponent(snApiComponent)
                .commentsListPresenterModule(new CommentsListPresenterModule(mCommentsListFragment))
                .mainActivityPresenterModule(new MainActivityPresenterModule(this))
                .build().inject(this);

    }

    @Override
    protected void onStart() {
        super.onStart();
        mHelper.bindService(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        mHelper.unBindService(this);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
    }

    private void setupViews() {

        mHotNewsListFragment = NewsListFragment.newInstance(getString(R.string.host, "/news"));
        mNewsListFragment = NewsListFragment.newInstance(getString(R.string.host, "/newest"));
        mCommentsListFragment = CommentsListFragment.newInstance();

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final ActionBar ab = getSupportActionBar();
        if (ab != null) {
            ab.setDisplayHomeAsUpEnabled(false);
        }
        ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
        if (viewPager != null) {
            SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
            viewPager.setAdapter(sectionsPagerAdapter);
            TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
            tabLayout.setupWithViewPager(viewPager);
        }
    }

    private Intent createEmailIntent() {
        Intent emailIntent = new Intent(Intent.ACTION_SEND);
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{
                "ghanguo@gmail.com"
        });
        StringBuilder builder = new StringBuilder();
        builder.append(getString(R.string.app_name)).append(" v")
                .append(AppUtils.getVersionName(getApplicationContext()))
                .append(getString(R.string.feedback));
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, builder.toString());
        emailIntent.setType("message/rfc822");
        return emailIntent;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.removeItem(R.id.menu_login);
        menu.removeItem(R.id.menu_logout);
        if (mSessionManager.isValid()) {
            menu.add(Menu.NONE, R.id.menu_logout, Menu.NONE, R.string.menu_logout);
        } else {
            menu.add(Menu.NONE, R.id.menu_login, Menu.NONE, R.string.menu_login);
        }
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_settings:
                Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                        "mainactivity_menu_settings", 0L);
                startActivity(new Intent(this, AboutActivity.class));
                return true;
            case R.id.menu_feedback:
                Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                        "mainactivity_menu_feedback", 0L);
                if (ActivityUtils.isIntentAvailable(getApplicationContext(), mFeedbackEmailIntent)) {
                    startActivity(mFeedbackEmailIntent);
                } else {
                    Toast.makeText(getApplicationContext(), R.string.error_noemailapp,
                            Toast.LENGTH_LONG).show();
                }
                return true;
            case R.id.menu_login: {
                Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                        "mainactivity_menu_login", 0L);
                Intent intent = new Intent(this, LoginActivity.class);
                startActivity(intent);
            }
            return true;
            case R.id.menu_logout:
                Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                        "mainactivity_menu_logout", 0L);
                mSessionManager.clear();
                CDToast.showToast(this, R.string.tip_logout_success);
                mMainActivityPresenter.logout();
                return true;
            case R.id.menu_show_comment:
                showDiscussFragment();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void showDiscussFragment() {
        showDiscussFragment(mSnNew, null);
    }

    private void showDiscussFragment(SNNew snNew, String discussUrl) {
        Bundle args = new Bundle();
        if (snNew != null) {
            args.putParcelable(DiscussActivity.ARG_SNNEW, snNew);
            args.putString(DiscussActivity.ARG_DISCUSS_URL, mSnNew.getDiscussURL());
        } else {
            args.putString(DiscussActivity.ARG_DISCUSS_URL, discussUrl);
        }
        DiscussFragment fragment = new DiscussFragment();
        fragment.setArguments(args);
        getSupportFragmentManager().beginTransaction()
                .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
                .replace(R.id.fragment_container, fragment, TAG_DISCUSS).commit();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    @Override
    public void onLogoutResult(boolean result) {
        CDToast.showToast(getApplicationContext(), result ? R.string.tip_logout_success
                : R.string.tip_logout_failure);
    }

    @Override
    public void onUpVoteFailure(Throwable e) {
        Tracker.getInstance().sendException("up vote error!", e, false);
        CDToast.showToast(this, getString(R.string.tip_vote_failure));
    }

    @Override
    public void onUpVoteSuccess(Status status) {
        switch (status.code) {
            case Status.CODE_COOKIE_VALID:
                startActivity(new Intent(this, LoginActivity.class));
                CDToast.showToast(this, R.string.tip_cookie_invalid);
                break;
            case Status.CODE_REPEAT:
                CDToast.showToast(this, getString(R.string.tip_vote_duplicate));
                break;
            case Status.CODE_SUCCESS:
                CDToast.showToast(this, R.string.tip_vote_success);
                break;
            default:
                break;
        }
    }

    /* no-op */
    @Override
    public void setPresenter(MainActivityContract.Presenter presenter) {

    }

    @Override
    public boolean isActive() {
        return !isFinishing();
    }

    private class SectionsPagerAdapter extends FragmentPagerAdapter {

        private String[] titles;

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
            titles = getResources().getStringArray(R.array.section_titles);
        }

        @Override
        public Fragment getItem(int arg0) {
            switch (arg0) {
                case 0:
                    return mHotNewsListFragment;
                case 1:
                    return mNewsListFragment;
                case 2:
                    return mCommentsListFragment;
                default:
                    throw new IllegalArgumentException();
            }
        }

        @Override
        public int getCount() {
            return titles.length;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return titles[position];
        }

        @Override
        public long getItemId(int position) {
            return super.getItemId(position);
        }

    }

    @Override
    public void onNewsSelected(int position, final SNNew snNew) {
        mSnNew = snNew;
        CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(mHelper.getSession());
        //设置 toolbar 颜色
        builder.setToolbarColor(0XFF33B5E5);
        CustomTabsIntent customTabsIntent = builder.build();
        mHelper.launchUrl(this, snNew.getUrl(), customTabsIntent, new CustomTabsActivityHelper.OnCustomTabsInvalidListener() {
            @Override
            public void onInvalid(String url) {
                ActivityUtils.openArticle(MainActivity.this, snNew);
            }
        });
    }

    private void showBrowseFragment(SNNew snNew) {
        BrowseFragment browseFragment = (BrowseFragment) getSupportFragmentManager()
                .findFragmentByTag(TAG_BROWSE);
        if (browseFragment != null) {
            browseFragment.setTitle(snNew.getTitle());
            browseFragment.load(snNew.getUrl());
        } else {
            BrowseFragment fragment = new BrowseFragment();
            Bundle bundle = new Bundle();
            bundle.putString(BrowseActivity.EXTRA_URL, snNew.getUrl());
            bundle.putString(BrowseActivity.EXTRA_TITLE, snNew.getTitle());
            fragment.setArguments(bundle);
            getSupportFragmentManager().beginTransaction()
                    .setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right)
                    .replace(R.id.fragment_container, fragment, TAG_BROWSE).commit();
        }
    }


    @Override
    public void onShowArticleSelected(SNNew snNew) {
        mSnNew = snNew;
        showBrowseFragment(snNew);
    }

    @Override
    public void onUpVoteSelected(String postId) {
        mMainActivityPresenter.upVote(postId);
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/ShareHelper.java
================================================
/*
 * Copyright (C) 2013 HalZhang.
 *
 * http://www.gnu.org/licenses/gpl-3.0.txt
 */
package com.halzhang.android.apps.startupnews.ui;


/**
 * StartupNews
 * <p>
 * </p>
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Jul 7, 2013
 */
public class ShareHelper {
    //TODO implements
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/SubmitActivity.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */
package com.halzhang.android.apps.startupnews.ui;

import android.support.v4.app.FragmentActivity;

/**
 * StartupNews
 * <p>
 * </p>
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 11, 2013
 */
public class SubmitActivity extends FragmentActivity {

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/BaseFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.fragment;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;

import rx.subscriptions.CompositeSubscription;

/**
 * Created by Hal on 16/6/18.
 */
public class BaseFragment extends Fragment {

    protected CompositeSubscription mCompositeSubscription;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCompositeSubscription = new CompositeSubscription();
    }

    @Override
    public void onDestroyView() {
        mCompositeSubscription.clear();
        super.onDestroyView();
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/CommentsListFragment.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.ui.fragment;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.CommentsListContract;
import com.halzhang.android.apps.startupnews.ui.DiscussActivity;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNComments;

import java.util.ArrayList;

/**
 * StartupNews
 * <p>
 * 评论
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 7, 2013
 */
public class CommentsListFragment extends SwipeRefreshRecyclerFragment implements CommentsListContract.View {

    private static final String LOG_TAG = CommentsListFragment.class.getSimpleName();

    // private ArrayList<SNComment> mComments = new ArrayList<SNComment>(24);

    private CommentsListContract.Presenter mPresenter;

    private CommentsAdapter mAdapter;


    private SNComments mSnComments = new SNComments();

    private static final String NEWCOMMENTS_URL_PATH = "/newcomments";

    public CommentsListFragment() {
    }

    public static CommentsListFragment newInstance() {
        return new CommentsListFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new CommentsAdapter();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mRecyclerView.setAdapter(mAdapter);
        if (mAdapter.isEmpty()) {
            mPresenter.getComments(getString(R.string.host, NEWCOMMENTS_URL_PATH));
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onRefreshData() {
        super.onRefreshData();
        Tracker.getInstance().sendEvent("ui_action", "pull_down_list_view_refresh",
                "comments_list_fragment_pull_down_list_view_refresh", 0L);
        mPresenter.getComments(getString(R.string.host, NEWCOMMENTS_URL_PATH));

    }

    @Override
    protected void onLoadMore() {
        super.onLoadMore();
        Tracker.getInstance().sendEvent("ui_action", "pull_up_list_view_refresh",
                "comments_list_fragment_pull_up_list_view_refresh", 0L);
        mPresenter.getMoreComments();
    }

    @Override
    public void onSuccess(ArrayList<SNComment> snComments) {
        onRefreshComplete();
        mSnComments.setSnComments(snComments);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void onFailure(Throwable e) {
        Log.e(LOG_TAG, "", e);
        onRefreshComplete();
        Tracker.getInstance().sendException("CommentsTask", e, false);
        Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_LONG).show();
    }

    @Override
    public void onAtEnd() {
        onRefreshComplete();
        Toast.makeText(getActivity(), R.string.tip_last_page, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void setPresenter(CommentsListContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }

    private class CommentsAdapter extends RecyclerView.Adapter<CommentsAdapter.ViewHolder> {

        public final class ViewHolder extends RecyclerView.ViewHolder {

            public final TextView mUserId;

            public final TextView mCreated;

            public final TextView mCommentText;

            public final TextView mArtistTitle;

            public final View mItemView;

            public ViewHolder(View itemView) {
                super(itemView);
                mItemView = itemView;
                mUserId = (TextView) itemView.findViewById(R.id.comment_item_user_id);
                mCreated = (TextView) itemView.findViewById(R.id.comment_item_created);
                mCommentText = (TextView) itemView.findViewById(R.id.comment_item_text);
                mArtistTitle = (TextView) itemView.findViewById(R.id.comment_item_artist_titile);
            }
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.comment_list_item, null));
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            SNComment comment = mSnComments.getSnComments().get(position);
            holder.mUserId.setText(comment.getUser().getId());
            holder.mCreated.setText(comment.getCreated());
            holder.mCommentText.setText(comment.getText());
            holder.mArtistTitle.setText(getString(R.string.comment_artist_title, comment.getArtistTitle()));
            holder.mItemView.setTag(position);
            holder.mItemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Tracker.getInstance().sendEvent("ui_action", "list_item_click", "comments_list_fragment_list_item_click", 0L);
                    int position = (int) v.getTag();
                    SNComment comment = mSnComments.getSnComments().get(position);
                    Intent intent = new Intent(getActivity(), DiscussActivity.class);
                    intent.putExtra(DiscussActivity.ARG_DISCUSS_URL, comment.getDiscussURL());
                    startActivity(intent);
                }
            });
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getItemCount() {
            return mSnComments.size();
        }

        public boolean isEmpty() {
            return getItemCount() == 0;
        }
    }

    @Override
    public int getViewLayout() {
        return R.layout.refresh_recycler_view_layout;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/LoginFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.fragment;


import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.halzhang.android.apps.startupnews.Constants;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.presenter.LoginContract;
import com.halzhang.android.common.CDToast;

import rx.Subscription;

/**
 * A simple {@link Fragment} subclass.
 * Use the {@link LoginFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class LoginFragment extends BaseFragment implements LoginContract.View {

    // UI references.
    private EditText mUsernameView;

    private EditText mPasswordView;

    private View mLoginFormView;

    private View mLoginStatusView;

    private TextView mLoginStatusMessageView;

    private LoginContract.Presenter mPresenter;

    public LoginFragment() {

    }

    public static LoginFragment newInstance() {
        return new LoginFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onAttach(Context activity) {
        super.onAttach(activity);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_login, null);
        mUsernameView = (EditText) view.findViewById(R.id.username);
        mPasswordView = (EditText) view.findViewById(R.id.password);
        mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                if (id == R.id.login || id == EditorInfo.IME_NULL) {
                    attemptLogin();
                    return true;
                }
                return false;
            }
        });

        mLoginFormView = view.findViewById(R.id.login_form);
        mLoginStatusView = view.findViewById(R.id.login_status);
        mLoginStatusMessageView = (TextView) view.findViewById(R.id.login_status_message);
        mLoginStatusMessageView.setText(R.string.login_progress_init);
        showProgress(false);
        Button loginBtn = (Button) view.findViewById(R.id.btn_login);
        loginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                attemptLogin();
            }
        });
        return view;
    }

    /**
     * Attempts to sign in or register the account specified by the login
     * form. If there are form errors (invalid email, missing fields, etc.),
     * the errors are presented and no actual login attempt is made.
     */
    public void attemptLogin() {
        // Reset errors.
        mUsernameView.setError(null);
        mPasswordView.setError(null);

        String username = mUsernameView.getText().toString();
        String password = mPasswordView.getText().toString();

        boolean cancel = false;
        View focusView = null;

        if (TextUtils.isEmpty(password)) {
            mPasswordView.setError(getString(R.string.error_field_required));
            focusView = mPasswordView;
            cancel = true;
        }
        if (TextUtils.isEmpty(username)) {
            mUsernameView.setError(getString(R.string.error_field_required));
            focusView = mUsernameView;
            cancel = true;
        }

        if (cancel) {
            focusView.requestFocus();
        } else {
            mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
            showProgress(true);
            mPresenter.login(username, password);
        }
    }

    /**
     * Shows the progress UI and hides the login form.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    public void showProgress(final boolean show) {
        if (getActivity() == null) {
            //防止 not attact to activity 出错
            return;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            int shortAnimTime = getResources().getInteger(
                    android.R.integer.config_shortAnimTime);

            mLoginStatusView.setVisibility(View.INVISIBLE);
            mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
                        }
                    });

            mLoginFormView.setVisibility(View.INVISIBLE);
            mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                        }
                    });
        } else {
            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
        }
    }


    @Override
    public void onLoginError(Throwable e) {
        e.printStackTrace();
    }

    @Override
    public void onLoginResult(String user) {
        Activity activity = getActivity();
        if (activity == null || activity.isFinishing()) {
            return;
        }
        showProgress(false);
        if (!TextUtils.isEmpty(user)) {
            CDToast.showToast(activity, R.string.tip_login_success);
            Intent intent = new Intent(Constants.IntentAction.ACTION_LOGIN);
            intent.putExtra(Constants.IntentAction.EXTRA_LOGIN_USER, user);
            activity.sendBroadcast(intent);
            activity.finish();
        } else {
            CDToast.showToast(activity, R.string.tip_login_failure);
        }
    }

    @Override
    public void addSubscription(Subscription subscription) {
        mCompositeSubscription.add(subscription);
    }

    @Override
    public void setPresenter(LoginContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/NewsListFragment.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.ui.fragment;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.TextView;
import android.widget.Toast;

import com.halzhang.android.apps.startupnews.Constants.IntentAction;
import com.halzhang.android.apps.startupnews.MyApplication;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.DaggerNewsListFragmentComponent;
import com.halzhang.android.apps.startupnews.presenter.NewsListContract;
import com.halzhang.android.apps.startupnews.presenter.NewsListPresenter;
import com.halzhang.android.apps.startupnews.presenter.NewsListPresenterModule;
import com.halzhang.android.apps.startupnews.ui.DiscussActivity;
import com.halzhang.android.apps.startupnews.utils.AppUtils;
import com.halzhang.android.common.CDLog;
import com.halzhang.android.startupnews.data.entity.SNFeed;
import com.halzhang.android.startupnews.data.entity.SNNew;

import java.util.ArrayList;

import javax.inject.Inject;

/**
 * StartupNews
 * <p>
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 7, 2013
 */
public class NewsListFragment extends SwipeRefreshRecyclerFragment implements NewsListContract.View {

    private static final String LOG_TAG = NewsListFragment.class.getSimpleName();

    @Inject
    NewsListPresenter mNewsListPresenter;

    private NewsListContract.Presenter mPresenter;

    @Override
    public void setPresenter(NewsListContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }

    @Override
    public void onSuccess(ArrayList<SNNew> snNews) {
        onRefreshComplete();
        mSnFeed.setSnNews(snNews);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void onFailure(Throwable e) {
        CDLog.w(LOG_TAG, "", e);
        onRefreshComplete();
        Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_LONG).show();
        Tracker.getInstance().sendException("NewsTask", e, false);
    }

    @Override
    public void onAtEnd() {
        onRefreshComplete();
        Toast.makeText(getActivity(), R.string.tip_last_page, Toast.LENGTH_SHORT).show();
    }

    /**
     * {@link NewsListFragment}选中监听器
     */
    public interface OnNewsSelectedListener {
        /**
         * 处理news被选中事件
         *
         * @param position list position
         * @param snNew    {@link SNNew}
         */
        public void onNewsSelected(int position, SNNew snNew);
    }

    private OnNewsSelectedListener mNewsSelectedListener;


    private String mNewsURL;

    public static final String ARG_URL = "new_url";

    private SNFeed mSnFeed = new SNFeed();

    private NewsAdapter mAdapter;

    public NewsListFragment() {

    }

    public static NewsListFragment newInstance(String url) {
        NewsListFragment fragment = new NewsListFragment();
        Bundle args = new Bundle(1);
        args.putString(ARG_URL, url);
        fragment.setArguments(args);
        return fragment;
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (IntentAction.ACTION_LOGIN.equals(action)) {
                String user = intent.getStringExtra(IntentAction.EXTRA_LOGIN_USER);
                if (!TextUtils.isEmpty(user)) {
                    mPresenter.getFeed(mNewsURL);
                }
            }
        }
    };

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mNewsSelectedListener = (OnNewsSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnNewsSelectedListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mNewsSelectedListener = null;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new NewsAdapter();
        Bundle args = getArguments();
        if (args != null) {
            mNewsURL = args.getString(ARG_URL);
        } else {
            mNewsURL = getString(R.string.host, "/news");
        }
        IntentFilter filter = new IntentFilter();
        filter.addAction(IntentAction.ACTION_LOGIN);
        getActivity().registerReceiver(mReceiver, filter);
        SnApiComponent snApiComponent = ((MyApplication) getActivity().getApplication()).getSnApiComponent();
        DaggerNewsListFragmentComponent.builder().snApiComponent(snApiComponent)
                .newsListPresenterModule(new NewsListPresenterModule(this)).build().inject(this);
    }

    @Override
    protected int getViewLayout() {
        return R.layout.refresh_recycler_view_layout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mRecyclerView.setAdapter(mAdapter);
        if (mAdapter.isEmpty()) {
            mPresenter.getFeed(mNewsURL);
            mSwipeRefreshLayout.post(new Runnable() {
                @Override
                public void run() {
                    mSwipeRefreshLayout.setRefreshing(true);
                }
            });
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        CDLog.d(LOG_TAG, this.toString() + " destroy view!");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        getActivity().unregisterReceiver(mReceiver);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        int position = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
        final SNNew snNew = mSnFeed.getSnNews().get(position);
        Log.i(LOG_TAG, snNew.toString());
        switch (item.getItemId()) {
            case R.id.menu_show_comment:
                Tracker.getInstance().sendEvent("ui_action", "context_item_selected",
                        "newslistfragment_menu_show_comment", 0L);
                openDiscuss(snNew);
                return true;
            case R.id.menu_show_article:
                Tracker.getInstance().sendEvent("ui_action", "context_item_selected",
                        "newslistfragment_menu_show_acticle", 0L);
                openArticle(position - 1, snNew);
                return true;
            case R.id.menu_up_vote:
                Tracker.getInstance().sendEvent("ui_action", "context_item_selected",
                        "newslistfragment_menu_upvote", 0L);

                // TODO: 16/8/14 投票操作

                return true;
            default:
                break;
        }
        return true;
    }

    @Override
    protected void onRefreshData() {
        super.onRefreshData();
        Tracker.getInstance().sendEvent("ui_action", "pull_down_list_view_refresh", "news_list_fragment_pull_down_list_view_refresh", 0L);
        mPresenter.getFeed(mNewsURL);
    }

    @Override
    protected void onLoadMore() {
        super.onLoadMore();
        Tracker.getInstance().sendEvent("ui_action", "pull_up_list_view_refresh", "news_list_fragment_pull_up_list_view_refresh", 0L);

        mPresenter.getMoreFeed();
    }

    private void openArticle(int position, SNNew snNew) {
        if (mNewsSelectedListener != null) {
            mNewsSelectedListener.onNewsSelected(position, snNew);
        }
    }

    private void openDiscuss(SNNew snNew) {
        if (snNew == null) {
            return;
        }

        DiscussActivity.start(getActivity(),snNew.getDiscussURL(),snNew);
    }

    private class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {

        public final class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {

            public final TextView user;

            public final TextView createAt;

            public final TextView title;

            public final TextView subText;

            public final TextView domain;

            public final View mView;

            public ViewHolder(View itemView) {
                super(itemView);
                mView = itemView;
                user = (TextView) itemView.findViewById(R.id.news_item_user);
                createAt = (TextView) itemView.findViewById(R.id.news_item_createat);
                title = (TextView) itemView.findViewById(R.id.news_item_title);
                subText = (TextView) itemView.findViewById(R.id.news_item_subtext);
                domain = (TextView) itemView.findViewById(R.id.news_item_domain);
                mView.setOnCreateContextMenuListener(this);
                mView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int position = (int) v.getTag();
                        SNNew entity = mSnFeed.getSnNews().get(position);
                        Tracker.getInstance().sendEvent("ui_action", "list_item_click",
                                "news_list_fragment_list_item_click", 0L);
                        mAdapter.notifyDataSetChanged();
                        openArticle(position, entity);
                    }
                });
            }

            @Override
            public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
                getActivity().getMenuInflater().inflate(R.menu.fragment_news, menu);
            }
        }


        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.news_list_item, null));
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            final SNNew entity = mSnFeed.getSnNews().get(position);
            holder.user.setText(entity.getUser().getId());
            holder.title.setText(entity.getTitle());
            holder.subText.setText(getString(R.string.news_subtext, entity.getPoints(),
                    entity.getCommentsCount()));
            holder.createAt.setText(entity.getCreateat());
            holder.domain.setText(entity.getUrlDomain());
            int textColor = AppUtils.getMyApplication(getActivity()).isHistoryContains(
                    entity.getUrl()) ? Color.GRAY : Color.BLACK;
            holder.title.setTextColor(textColor);
            holder.subText.setTextColor(textColor);
            holder.domain.setTextColor(textColor);
            holder.createAt.setTextColor(textColor);
            holder.mView.setTag(position);
        }

        @Override
        public int getItemCount() {
            return mSnFeed.getSnNews().size();
        }

        public boolean isEmpty() {
            return getItemCount() == 0;
        }

    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/SwipeRefreshRecyclerFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.fragment;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.ui.widgets.CardViewDividerDecoration;
import com.halzhang.android.apps.startupnews.ui.widgets.DividerDecoration;

public abstract class SwipeRefreshRecyclerFragment extends Fragment {

    private static final String LOG_TAG = SwipeRefreshRecyclerFragment.class.getSimpleName();

    protected SwipeRefreshLayout mSwipeRefreshLayout;
    protected RecyclerView mRecyclerView;
    private LinearLayoutManager mLinearLayoutManager;


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

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    protected abstract int getViewLayout();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(getViewLayout(), null);
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
        mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_layout);
        if (mRecyclerView == null || mSwipeRefreshLayout == null) {
            throw new IllegalArgumentException("mush be have RecyclerView and SwipeRefreshLayout");
        }
        mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light, android.R.color.holo_blue_light);
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                onRefreshData();
            }
        });
        mLinearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
        mRecyclerView.setLayoutManager(mLinearLayoutManager);
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                boolean enable = false;
                int visibleItemCount = mRecyclerView.getChildCount();
                int itemCount = mLinearLayoutManager.getItemCount();
                int firstVisibleItemPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
                int lastVisibleItemPosition = mLinearLayoutManager.findLastVisibleItemPosition();
//                if (visibleItemCount > 0) {
//                    boolean firstItemVisible = firstVisibleItemPosition == 0;
//                    boolean topOfFirstItemVisible = mLinearLayoutManager.getChildAt(0).getTop() == 0;
//                    enable = firstItemVisible && topOfFirstItemVisible;
//                    Log.d(LOG_TAG, "SwipeRefreshLayout enable: " + enable);
//                }
//                mSwipeRefreshLayout.setEnabled(enable);
                if (lastVisibleItemPosition == itemCount - 1) {
                    onLoadMore();
                }
            }
        });
        mRecyclerView.addItemDecoration(new CardViewDividerDecoration(getActivity()));
        return view;
    }

    /**
     * 刷新数据
     */
    protected void onRefreshData() {
    }

    /**
     * 加载更多
     */
    protected void onLoadMore() {
    }

    protected void onRefreshComplete() {
        if (mSwipeRefreshLayout != null) {
            mSwipeRefreshLayout.setRefreshing(false);
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }


}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/phone/BrowseActivity.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.ui.phone;

import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;
import com.halzhang.android.apps.startupnews.ui.tablet.BrowseFragment;

import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.Window;

/**
 * StartupNews
 * <p>
 * 浏览页面
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 7, 2013
 */
public class BrowseActivity extends BaseFragmentActivity {

    // private static final String LOG_TAG =
    // BrowseActivity.class.getSimpleName();

    public static final String EXTRA_URL = "extra_url";

    public static final String EXTRA_TITLE = "extra_title";

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.activity_browse);
        String mOriginalUrl = getIntent().getStringExtra(EXTRA_URL);
        if (TextUtils.isEmpty(mOriginalUrl)) {
            finish();
            return;
        }
        String mTitle = getIntent().getStringExtra(EXTRA_TITLE);

        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayShowTitleEnabled(true);
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        BrowseFragment fragment = new BrowseFragment();
        Bundle bundle = new Bundle();
        bundle.putString(EXTRA_URL, mOriginalUrl);
        bundle.putString(EXTRA_TITLE, mTitle);
        fragment.setArguments(bundle);
        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragment)
                .commitAllowingStateLoss();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }

    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/BrowseFragment.java
================================================

package com.halzhang.android.apps.startupnews.ui.tablet;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;
import com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;
import com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView;
import com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView.OnScrollChangedCallback;
import com.halzhang.android.apps.startupnews.ui.widgets.WebViewController;
import com.halzhang.android.apps.startupnews.utils.PreferenceUtils;
import com.halzhang.android.common.CDLog;

/**
 * 浏览器 Created by Hal on 13-5-25.
 */
public class BrowseFragment extends Fragment implements OnScrollChangedCallback {

    private static final String LOG_TAG = BrowseFragment.class.getSimpleName();

    private ObservableWebView mWebView;

    private WebViewController mWebViewController;

    private String mTitle;

    private String mOriginalUrl;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mWebViewController = new WebViewController(getActivity());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        setHasOptionsMenu(true);
        final View view = inflater.inflate(R.layout.fragment_browse, null);
        mWebView = (ObservableWebView) view.findViewById(R.id.webview);
        Activity activity = getActivity();
        mWebView.setOverScrollMode(View.OVER_SCROLL_NEVER);
        mWebView.setOnScrollChangedCallback(this);
        mWebViewController.initControllerView(mWebView, view);
        Bundle args = getArguments();
        if (args != null && args.containsKey(BrowseActivity.EXTRA_TITLE)
                && args.containsKey(BrowseActivity.EXTRA_URL)) {
            mTitle = args.getString(BrowseActivity.EXTRA_TITLE);
            mOriginalUrl = args.getString(BrowseActivity.EXTRA_URL);
            String mHtmlProvider = PreferenceUtils.getHtmlProvider(activity);
            final String url = mHtmlProvider + mOriginalUrl;
            mWebViewController.loadUrl(url);
        }
        return view;
    }

    @Override
    public void onDestroy() {
        mWebViewController.destroy();
        super.onDestroy();
    }

    public void setTitle(String title) {
        mTitle = title;
    }

    public void load(String url) {
        mOriginalUrl = url;
        String mHtmlProvider = PreferenceUtils.getHtmlProvider(getActivity());
        mWebViewController.loadUrl(mHtmlProvider + mOriginalUrl);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.fragment_browse, menu);
        super.onCreateOptionsMenu(menu, inflater);
        MenuItem actionItem = menu.findItem(R.id.menu_share);
        ShareActionProvider actionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(actionItem);
        if (actionProvider == null) {
            return;
        }
        actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME);
        actionProvider.setShareIntent(createShareIntent());
        actionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {

            @Override
            public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
                /*
                 * 这里改变intent是没用的,intent只是一份拷贝,只能自己启动修改后的Intent
                 * 然而自己启动Intent并不会改变历史记录 增加setAllowPolicyChangeIntent方法解决这问题
                 */
                String packageName = intent.getComponent().getPackageName();
                CDLog.i(LOG_TAG, packageName);
                String shareContent = getShareContent();
                intent.putExtra(Intent.EXTRA_SUBJECT, mTitle);
                Tracker.getInstance().sendEvent("ui_action", "share", packageName, 0L);
                if (getString(R.string.weibo_package_name).equals(packageName)) {
                    intent.putExtra(Intent.EXTRA_TEXT, shareContent + " "
                            + getString(R.string.weibo_share_suffix));
                } else {
                    intent.putExtra(Intent.EXTRA_TEXT, shareContent);
                }
                return false;
            }
        });
    }

    /**
     * Creates a sharing {@link Intent}.
     *
     * @return The sharing intent.
     */
    private Intent createShareIntent() {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_SUBJECT, mTitle);
        intent.putExtra(Intent.EXTRA_TEXT, getShareContent());
        return intent;
    }

    private String getShareContent() {
        StringBuilder builder = new StringBuilder();
        return builder.append(mTitle).append(" ").append(mOriginalUrl).toString();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_original_url:
                Tracker.getInstance().sendEvent("ui_action", "options_item_selected", "browseactivity_menu_original_url", 0L);
                mWebViewController.loadUrl(mOriginalUrl);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onScroll(int l, int t, int oldl, int oldt) {

    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onScrollUp() {
        //TODO 需要判断 Activity 的类型
        BaseFragmentActivity activity = (BaseFragmentActivity) getActivity();
        activity.getSupportActionBar().show();
        mWebViewController.showBrowseBar();
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onScrollDown() {
        BaseFragmentActivity activity = (BaseFragmentActivity) getActivity();
        activity.getSupportActionBar().hide();
        mWebViewController.hideBrowseBar();
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/DiscussFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.tablet;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.DiscussContract;
import com.halzhang.android.apps.startupnews.ui.LoginActivity;
import com.halzhang.android.apps.startupnews.utils.ActivityUtils;
import com.halzhang.android.common.CDToast;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.SNNew;
import com.halzhang.android.startupnews.data.entity.Status;
import com.halzhang.android.startupnews.data.utils.SessionManager;

import javax.inject.Inject;

/**
 * 查看评论
 * Created by Hal on 13-5-26.
 */
public class DiscussFragment extends Fragment implements OnItemClickListener, DiscussContract.View {

    public static final String ARG_DISCUSS_URL = "discuss_url";
    public static final String ARG_SNNEW = "snnew";

    private static final String LOG_TAG = DiscussFragment.class.getSimpleName();


    private DiscussContract.Presenter mPresenter;

    @Override
    public void setPresenter(DiscussContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }

    @Override
    public void onGetDiscuss(SNDiscuss snDiscuss) {
        mSnDiscuss.clearComments();
        mSnDiscuss.copy(snDiscuss);
        setRefreshActionButtonState(false);
        wrapHeaderView(mSnDiscuss.getSnNew());
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void onGetDiscussFailure(Throwable e) {
        Tracker.getInstance().sendException("DiscussTask", e, false);
        Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onCommentSuccess(Status status) {

        switch (status.code){
            case Status.CODE_COOKIE_VALID:
                CDToast.showToast(getActivity(), R.string.tip_cookie_invalid);
                startActivity(new Intent(getActivity(), LoginActivity.class));
                Tracker.getInstance().sendEvent("ui_action_feedback",
                        "comment_feedback", getString(R.string.tip_cookie_invalid),
                        0L);
                break;
            case Status.CODE_SUCCESS:
                mCommentEdit.setText(null);
                CDToast.showToast(getActivity(), R.string.tip_comment_success);
                Tracker.getInstance().sendEvent("ui_action_feedback",
                        "comment_feedback", "success", 0L);
                loadData();
                break;
            default:
                Tracker.getInstance().sendEvent("ui_action_feedback",
                        "comment_feedback", status.message, 0L);
                CDToast.showToast(getActivity(), R.string.tip_comment_failure);
                break;
        }
    }

    @Override
    public void onCommentFailure(Throwable e) {
        CDToast.showToast(getActivity(), R.string.tip_comment_failure);
        Tracker.getInstance().sendException("comment error!", e, false);
    }

    @Override
    public void onSessionExpired() {
        Intent intent = new Intent(getActivity(), LoginActivity.class);
        startActivity(intent);
    }

    public interface OnMenuSelectedListener {
        public void onShowArticleSelected(SNNew snNew);

        public void onUpVoteSelected(String postId);
    }

    private SNDiscuss mSnDiscuss;

    private ListView mListView;

    private String mDiscussURL;

    private DiscussCommentAdapter mAdapter;

    private TextView mTitle;

    private TextView mSubTitle;

    private TextView mText;

    private Menu mOptionsMenu;

    private EditText mCommentEdit;

    private ImageButton mSendBtn;

    private OnMenuSelectedListener mListener;

    @Inject
    SessionManager mSessionManager;

    public DiscussFragment() {

    }

    public static DiscussFragment newInstance(String discussURL, SNNew snNew) {
        DiscussFragment fragment = new DiscussFragment();
        Bundle args = new Bundle();
        args.putString(ARG_DISCUSS_URL, discussURL);
        args.putParcelable(ARG_SNNEW, snNew);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (activity instanceof OnMenuSelectedListener) {
            mListener = (OnMenuSelectedListener) activity;
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        setHasOptionsMenu(true);
        View view = inflater.inflate(R.layout.fragment_discuss, null);
        mListView = (ListView) view.findViewById(android.R.id.list);
        mListView.setOnItemClickListener(this);
        mSnDiscuss = new SNDiscuss();
        Bundle args = getArguments();
        SNNew snNew = null;
        if (args != null && args.containsKey(ARG_DISCUSS_URL)) {
            mDiscussURL = args.getString(ARG_DISCUSS_URL);
        } else {
            throw new IllegalArgumentException("Discuss URL is required!");
        }
        if (args.containsKey(ARG_SNNEW)) {
            snNew = args.getParcelable(ARG_SNNEW);
            mSnDiscuss.setSnNew(snNew);
        }
        mAdapter = new DiscussCommentAdapter();
        View headerView = inflater.inflate(R.layout.discuss_header_view, null);
        mTitle = (TextView) headerView.findViewById(R.id.discuss_news_title);
        mSubTitle = (TextView) headerView.findViewById(R.id.discuss_news_subtitle);
        mText = (TextView) headerView.findViewById(R.id.discuss_text);

        mSendBtn = (ImageButton) view.findViewById(R.id.discuss_comment_send_btn);
        mSendBtn.setEnabled(false);
        mSendBtn.setOnClickListener(mSendBtnClickListener);
        mCommentEdit = (EditText) view.findViewById(R.id.discuss_comment_edit);
        mCommentEdit.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                mSendBtn.setEnabled(s.length() > 0);
            }
        });
        mListView.addHeaderView(headerView);
        mListView.setAdapter(mAdapter);
        wrapHeaderView(snNew);
        loadData();
        return view;
    }

    private OnClickListener mSendBtnClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            Tracker.getInstance().sendEvent("ui_action", "view_clicked",
                    "discussactivity_button_comment", 0L);
            mPresenter.comment(mCommentEdit.getText().toString());
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        mOptionsMenu = menu;
        inflater.inflate(R.menu.fragment_discuss, menu);
    }

    private void wrapHeaderView(SNNew snNew) {
        if (snNew != null) {
            mTitle.setText(snNew.getTitle());
            mSubTitle.setText(Html.fromHtml(snNew.getSubText()));
            if (snNew.isDiscuss()) {
                mText.setVisibility(View.VISIBLE);
                mText.setText(snNew.getText());
            } else {
                mText.setVisibility(View.GONE);
            }
        }
    }

    public void setRefreshActionButtonState(boolean refreshing) {
        if (mOptionsMenu == null) {
            Log.i(LOG_TAG, "Option menu is null!");
            return;
        }

        final MenuItem refreshItem = mOptionsMenu.findItem(R.id.menu_refresh);
        if (refreshItem != null) {
            if (refreshing) {
                refreshItem.setActionView(R.layout.actionbar_indeterminate_progress);
            } else {
                refreshItem.setActionView(null);
            }
        }
    }

    public void loadData() {
        mPresenter.getDiscuss(mDiscussURL);
        setRefreshActionButtonState(true);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_refresh:
                Tracker.getInstance().sendEvent("ui_action", "options_item_selected", "discussactivity_menu_refresh", 0L);
                loadData();
                return true;
            case R.id.menu_show_article:
                if (mListener != null) {
                    mListener.onShowArticleSelected(mSnDiscuss.getSnNew());
                }
                return true;
            case R.id.menu_up_vote:
                if (mListener != null) {
                    mListener.onUpVoteSelected(mSnDiscuss.getSnNew().getPostID());
                }
            default:
                return super.onOptionsItemSelected(item);
        }

    }

    private class DiscussCommentAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return mSnDiscuss.commentSize();
        }

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

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = LayoutInflater.from(getActivity()).inflate(
                        R.layout.discuss_comment_item, null);
                holder.mUserId = (TextView) convertView
                        .findViewById(R.id.discuss_comment_item_user_id);
                holder.mCreated = (TextView) convertView
                        .findViewById(R.id.discuss_comment_item_created);
                holder.mCommentText = (TextView) convertView
                        .findViewById(R.id.discuss_comment_item_text);
                holder.mArtistTitle = (TextView) convertView
                        .findViewById(R.id.discuss_comment_item_artist_titile);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            SNComment comment = mSnDiscuss.getComments().get(position);
            holder.mUserId.setText(comment.getUser().getId());
            holder.mCreated.setText(comment.getCreated());
            holder.mCommentText.setText(comment.getText());
            holder.mArtistTitle.setVisibility(View.GONE);
            return convertView;
        }

        class ViewHolder {
            TextView mUserId;

            TextView mCreated;

            TextView mCommentText;

            TextView mArtistTitle;
        }

    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (position == 0 && mListener == null) {
            Tracker.getInstance().sendEvent("ui_action", "list_item_click",
                    "discuss_activity_list_header_click", 0L);
            // 查看文章
            ActivityUtils.openArticle(getActivity(), mSnDiscuss.getSnNew());
        }
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/CardViewDividerDecoration.java
================================================
package com.halzhang.android.apps.startupnews.ui.widgets;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.halzhang.android.apps.startupnews.R;

/**
 * Divider for {@link RecyclerView}
 * Created by Hal on 15/7/19.
 */
public class CardViewDividerDecoration extends RecyclerView.ItemDecoration {

    private Drawable mDivider;
    private int mInsets;

    public CardViewDividerDecoration(Context context) {
        mDivider = new ColorDrawable(context.getResources().getColor(android.R.color.transparent));
        mInsets = context.getResources().getDimensionPixelSize(R.dimen.card_insets);
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        drawVertical(c, parent);
    }

    /**
     * Draw dividers underneath each child view
     */
    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin + mInsets;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //We can supply forced insets for each item view here in the Rect
        outRect.set(mInsets, mInsets, mInsets, mInsets);
    }
}

================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/DividerDecoration.java
================================================
package com.halzhang.android.apps.startupnews.ui.widgets;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.halzhang.android.apps.startupnews.R;

/**
 * Divider for {@link RecyclerView}
 * Created by Hal on 15/7/19.
 */
public class DividerDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = {android.R.attr.listDivider};

    private Drawable mDivider;
    private int mInsets;

    public DividerDecoration(Context context) {
        TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();

        mInsets = context.getResources().getDimensionPixelSize(R.dimen.card_insets);
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        drawVertical(c, parent);
    }

    /**
     * Draw dividers underneath each child view
     */
    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin + mInsets;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //We can supply forced insets for each item view here in the Rect
        outRect.set(mInsets, mInsets, mInsets, mInsets);
    }
}

================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/ObservableWebView.java
================================================
/*
 *
 * Copyright (C) 2013 HalZhang.
 * http://www.gnu.org/licenses/gpl-3.0.txt
 */

package com.halzhang.android.apps.startupnews.ui.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;

/**
 * StartupNews
 * <p>
 * </p>
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Aug 25, 2013
 */
public class ObservableWebView extends WebView {

    private final static int TOUCH_STATE_REST = 0;

    private final static int TOUCH_STATE_SCROLL_UP = 1;

    private final static int TOUCH_STATE_SCROLL_DOWN = 2;

    private int mTouchState = TOUCH_STATE_REST;

    private OnScrollChangedCallback mOnScrollChangedCallback;

    public ObservableWebView(final Context context) {
        super(context);
    }

    public ObservableWebView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public ObservableWebView(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, oldl, oldt);
            if (t >= 0 && oldt >= 0) {
                if (oldt > t && mTouchState != TOUCH_STATE_SCROLL_UP) {
                    mTouchState = TOUCH_STATE_SCROLL_UP;
                    mOnScrollChangedCallback.onScrollUp();
                } else if (oldt < t && mTouchState != TOUCH_STATE_SCROLL_DOWN) {
                    mTouchState = TOUCH_STATE_SCROLL_DOWN;
                    mOnScrollChangedCallback.onScrollDown();
                }
            }
        }

    }

    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 static interface OnScrollChangedCallback {
        public void onScroll(int l, int t, int oldl, int oldt);

        /**
         * 向上滚动
         */
        public void onScrollUp();

        /**
         * 向下滚动
         */
        public void onScrollDown();
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/SwitchPreference.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */
package com.halzhang.android.apps.startupnews.ui.widgets;

import android.content.Context;
import android.preference.TwoStatePreference;
import android.util.AttributeSet;

/**
 * StartupNews
 * <p>
 * </p>
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 17, 2013
 */
public class SwitchPreference extends TwoStatePreference {

    public SwitchPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/WebViewController.java
================================================
package com.halzhang.android.apps.startupnews.ui.widgets;

import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;
import com.halzhang.android.apps.startupnews.utils.UIUtils;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageButton;

import java.lang.ref.WeakReference;

/**
 * WebView and Browse Bar Controller
 *
 * @author Hal
 */
public class WebViewController implements OnClickListener {

    private WeakReference<Activity> mActivityRef;

    private WebView mWebView;

    private ImageButton mBackButton;
    private ImageButton mForwardButton;
    private ImageButton mReadabilityButton;
    private ImageButton mRefreshButton;
    private ImageButton mWebSiteButton;

    private View mToolBar;

    private String mCurrentUrl;

    public WebViewController(Activity activity) {
        mActivityRef = new WeakReference<Activity>(activity);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @SuppressLint("SetJavaScriptEnabled")
    public void initControllerView(WebView webView, View view) {
        if (webView == null || view == null) {
            return;
        }
        mWebView = webView;
        mWebView.setWebChromeClient(new MyWebChromeClient());
        mWebView.setWebViewClient(new MyWebViewClient());

        WebSettings settings = mWebView.getSettings();
        settings.setSupportZoom(true);
        settings.setBuiltInZoomControls(true);
        if (UIUtils.hasHoneycomb()) {
            settings.setDisplayZoomControls(false);
        }
        settings.setJavaScriptEnabled(true);
        settings.setUseWideViewPort(true);
        settings.setLoadWithOverviewMode(true);
        mBackButton = (ImageButton) view.findViewById(R.id.browse_back);
        mForwardButton = (ImageButton) view.findViewById(R.id.browse_forward);
        mReadabilityButton = (ImageButton) view.findViewById(R.id.browse_readability);
        mRefreshButton = (ImageButton) view.findViewById(R.id.browse_refresh);
        mWebSiteButton = (ImageButton) view.findViewById(R.id.browse_website);

        mToolBar = view.findViewById(R.id.browse_bar);

        mBackButton.setOnClickListener(this);
        mForwardButton.setOnClickListener(this);
        mReadabilityButton.setOnClickListener(this);
        mRefreshButton.setOnClickListener(this);
        mWebSiteButton.setOnClickListener(this);
    }

    private class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
            setCurrentUrl(url);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            setCurrentUrl(url);
        }
    }

    private void setCurrentUrl(String url) {
        mCurrentUrl = url;
    }

    private String getCurrentUrl() {
        return mCurrentUrl; //TextUtils.isEmpty(mCurrentUrl) ? mOriginalUrl : mCurrentUrl;
    }

    private class MyWebChromeClient extends WebChromeClient {

        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
            int progress = (Window.PROGRESS_END - Window.PROGRESS_START) / 100 * newProgress;
            Activity activity = mActivityRef.get();
            if (activity != null && activity instanceof BaseFragmentActivity) {
                ((BaseFragmentActivity) activity).setSupportProgress(progress);
            }
        }

        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
            mActivityRef.get().setTitle(title);
        }

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.browse_back:
                back();
                break;
            case R.id.browse_forward:
                forward();
                break;
            case R.id.browse_readability:
                readability();
                break;
            case R.id.browse_refresh:
                refresh();
                break;
            case R.id.browse_website:
                webSite();
                break;
            default:
                break;
        }

    }

    private void back() {
        Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                "browseactivity_menu_back", 0L);
        if (mWebView.canGoBack()) {
            mWebView.goBack();
        }
    }

    private void forward() {
        Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                "browseactivity_menu_forward", 0L);
        if (mWebView.canGoForward()) {
            mWebView.goForward();
        }
    }

    private void readability() {
        Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                "browseactivity_menu_readability", 0L);
        if (TextUtils.isEmpty(mCurrentUrl)) {
            return;
        }
        mWebView.loadUrl("http://www.readability.com/m?url=" + getCurrentUrl());
    }

    private void refresh() {
        Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                "browseactivity_menu_refresh", 0L);
        mWebView.reload();
    }

    private void webSite() {
        // 打开原链接,还是转码的链接呢?
        Tracker.getInstance().sendEvent("ui_action", "options_item_selected",
                "browseactivity_menu_website", 0L);
        if (TextUtils.isEmpty(mCurrentUrl)) {
            return;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(getCurrentUrl()));
        mActivityRef.get().startActivity(intent);
    }

    public WebView getWebView() {
        return mWebView;
    }

    public void destroy() {
        if (mWebView != null) {
            ((ViewGroup) mWebView.getParent()).removeAllViews();
            mWebView.clearHistory();
            mWebView.clearCache(true);
            mWebView.loadUrl("about:blank");
            mWebView.pauseTimers();
            mWebView.destroy();
            mWebView = null;
        }
        mActivityRef.clear();
    }

    public void loadUrl(String url) {
        if (TextUtils.isEmpty(url) || url.equals(mCurrentUrl)) {
            return;
        }
        mWebView.clearHistory();
        mWebView.loadUrl(url);
    }

    public void showBrowseBar() {
        if (mToolBar != null) {
            Animation animation = AnimationUtils.loadAnimation(mActivityRef.get(), R.anim.push_up_in);
            mToolBar.startAnimation(animation);
            mToolBar.setVisibility(View.VISIBLE);
        }
    }

    public void hideBrowseBar() {
        if (mToolBar != null) {
            Animation animation = AnimationUtils.loadAnimation(mActivityRef.get(), R.anim.push_down_out);
            mToolBar.startAnimation(animation);
            mToolBar.setVisibility(View.GONE);
        }
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityScoped.java
================================================
package com.halzhang.android.apps.startupnews.utils;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Scope;

/**
 * Created by Hal on 16/6/12.
 */
@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScoped {
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityUtils.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.utils;

import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;

import com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;
import com.halzhang.android.startupnews.data.entity.SNNew;

import java.util.List;

/**
 * StartupNews
 * <p>
 * </p>
 *
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 22, 2013
 */
public class ActivityUtils {

    public static void openArticle(Activity activity, SNNew snNew) {
        if (snNew == null || activity == null) {
            return;
        }
        Intent intent = null;
        if (PreferenceUtils.isUseInnerBrowse(activity)) {
            intent = new Intent(activity, BrowseActivity.class);
            intent.putExtra(BrowseActivity.EXTRA_URL, snNew.getUrl());
            intent.putExtra(BrowseActivity.EXTRA_TITLE, snNew.getTitle());
        } else {
            intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse(PreferenceUtils.getHtmlProvider(activity) + snNew.getUrl()));
        }
        activity.startActivity(intent);
        AppUtils.getMyApplication(activity).addHistory(snNew.getUrl());
    }

    /**
     * With window Animations
     *
     * @param activity
     * @param snNew
     * @param v
     */
    public static void openActicle(Activity activity, SNNew snNew, View v) {
        if (snNew == null || activity == null) {
            return;
        }
        Bundle b = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            // b = ActivityOptions.makeScaleUpAnimation(view, 0, 0,
            // view.getWidth(),
            // view.getHeight()).toBundle();
            Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(),
                    Bitmap.Config.ARGB_8888);
            bitmap.eraseColor(Color.WHITE);
            b = ActivityOptions.makeThumbnailScaleUpAnimation(v, bitmap, 0, 0).toBundle();
        }
        Intent intent = null;
        if (PreferenceUtils.isUseInnerBrowse(activity)) {
            intent = new Intent(activity, BrowseActivity.class);
            intent.putExtra(BrowseActivity.EXTRA_URL, snNew.getUrl());
            intent.putExtra(BrowseActivity.EXTRA_TITLE, snNew.getTitle());
        } else {
            intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse(PreferenceUtils.getHtmlProvider(activity) + snNew.getUrl()));
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            activity.startActivity(intent, b);
        } else {
            activity.startActivity(intent);
        }
    }

    /**
     * 判断Intent是否可用
     *
     * @param context
     * @param intent
     * @return
     */
    public static boolean isIntentAvailable(Context context, Intent intent) {
        final PackageManager packageManager = context.getPackageManager();
        List<ResolveInfo> list = packageManager.queryIntentActivities(intent,
                PackageManager.GET_ACTIVITIES);
        return list.size() > 0;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/AppUtils.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.utils;

import com.halzhang.android.apps.startupnews.MyApplication;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;

/**
 * StartupNews
 * <p>
 * </p>
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 12, 2013
 */
public class AppUtils {

    public static String getVersionName(Context context) {
        try {

            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),
                    0);
            return info.versionName;
        } catch (NameNotFoundException e) {
            return "";
        }
    }

    public static int getVersionCode(Context context) {
        try {

            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),
                    0);
            return info.versionCode;
        } catch (NameNotFoundException e) {
            return 0;
        }
    }

    public static MyApplication getMyApplication(Context context) {
        return (MyApplication) context.getApplicationContext();
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/CrashHandler.java
================================================

package com.halzhang.android.apps.startupnews.utils;

import com.google.analytics.tracking.android.EasyTracker;
import com.halzhang.android.apps.startupnews.BuildConfig;
import com.halzhang.android.apps.startupnews.Constants;
import com.halzhang.android.apps.startupnews.MyApplication;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.common.CDLog;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * Crash info collection
 */
public class CrashHandler implements UncaughtExceptionHandler {

    private static final String LOG_TAG = CrashHandler.class.getSimpleName();

    private static CrashHandler me = null;

    private Context mContext;

    private Map<String, String> infos = new HashMap<String, String>();

    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());

    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
        if (me == null) {
            me = new CrashHandler();
        }
        return me;
    }

    public void init(Context context) {
        mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (BuildConfig.DEBUG) {
            Log.e(LOG_TAG, ex.getMessage(), ex);
            handleException(ex);
        }
        Tracker.getInstance().sendException(ex.getMessage(), ex, true);
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(0);
    }

    private boolean handleException(Throwable ex) {
        if (ex != null) {
            collectDeviceInfo(mContext);
            saveCrashInfo2File(ex);
            return true;
        }
        return false;
    }

    private void collectDeviceInfo(Context ctx) {
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                infos.put("versionName", versionName);
                infos.put("versionCode", versionCode);
            }
        } catch (NameNotFoundException e) {
        }
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
                //Log.d(LOG_TAG, field.getName() + " : " + field.get(null));
            } catch (Exception e) {
            }
        }
    }

    /**
     */
    private String saveCrashInfo2File(Throwable ex) {

        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }

        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        sb.append(result);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatter.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".txt";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = Environment.getExternalStorageDirectory().getAbsolutePath()
                        + Constants.CRASH_LOG_DIR;
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(path + fileName);
                fos.write(sb.toString().getBytes());
                fos.close();
            }
            return fileName;
        } catch (Exception e) {
            Log.e(LOG_TAG, "an error occured while writing file...", e);
        }
        return null;
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsActivityHelper.java
================================================
package com.halzhang.android.apps.startupnews.utils;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.customtabs.CustomTabsCallback;
import android.support.customtabs.CustomTabsClient;
import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsServiceConnection;
import android.support.customtabs.CustomTabsSession;
import android.text.TextUtils;
import android.util.Log;

/**
 * 界面 helper
 * Created by Hal on 15/11/25.
 */
public class CustomTabsActivityHelper {

    private static final String TAG = CustomTabsActivityHelper.class.getSimpleName();


    private CustomTabsClient mClient;
    private CustomTabsSession mSession;
    private CustomTabsServiceConnection mConnection;

    public void bindService(Context context) {
        if (mConnection != null) {
            return;
        }
        String packageName = CustomTabsHelper.getPackageNameToUse(context);
        Log.i(TAG, "Package name: " + packageName);
        if (TextUtils.isEmpty(packageName)) {
            return;
        }
        mConnection = new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient customTabsClient) {
                Log.i(TAG, "Custom tabs service connected!");
                mClient = customTabsClient;
                mClient.warmup(0);
                mSession = mClient.newSession(new CustomTabsCallback());
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mClient = null;
                mSession = null;
            }
        };
        CustomTabsClient.bindCustomTabsService(context, packageName, mConnection);
    }

    public CustomTabsSession getSession() {
        if (mClient == null) {
            return null;
        }
        if (mSession == null) {
            mSession = mClient.newSession(new CustomTabsCallback());
        }
        return mSession;
    }

    public void unBindService(Context context) {
        if (mConnection == null) {
            return;
        }
        context.unbindService(mConnection);
        mConnection = null;
    }

    /**
     * 启动
     *
     * @param activity         {@link Activity}
     * @param url              链接
     * @param customTabsIntent {@link CustomTabsIntent},由用户自定义属性
     * @param listener         {@link com.halzhang.android.apps.startupnews.utils.CustomTabsActivityHelper.OnCustomTabsInvalidListener},没有处理,使用默认浏览器打开
     */
    public void launchUrl(Activity activity, String url, CustomTabsIntent customTabsIntent, OnCustomTabsInvalidListener listener) {
        String packageName = CustomTabsHelper.getPackageNameToUse(activity);
        if (TextUtils.isEmpty(packageName)) {
            if (listener != null) {
                listener.onInvalid(url);
            } else {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse(url));
                activity.startActivity(intent);
            }
        } else {
            customTabsIntent.launchUrl(activity, Uri.parse(url));
        }
    }

    /**
     * 无效监听
     */
    public interface OnCustomTabsInvalidListener {
        /**
         * 当 custom tabs 无效时处理
         *
         * @param url 链接
         */
        public void onInvalid(String url);
    }


}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsHelper.java
================================================
package com.halzhang.android.apps.startupnews.utils;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.support.customtabs.CustomTabsService;
import android.text.TextUtils;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * helper
 * Created by Hal on 15/11/25.
 */
public class CustomTabsHelper {

    private static final String TAG = "CustomTabsHelper";
    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 final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
            "android.support.customtabs.extra.KEEP_ALIVE";

    private static String sPackageNameToUse;

    private CustomTabsHelper() {
    }

    /**
     * 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;
    }

    /**
     * @return All possible chrome package names that provide custom tabs feature.
     */
    public static String[] getPackages() {
        return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
    }
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/DateUtils.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */
package com.halzhang.android.apps.startupnews.utils;

import android.content.Context;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * StartupNews
 * <p>
 * </p>
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 12, 2013
 */
public class DateUtils {
    
    public static String getLastUpdateLabel(Context context){
        final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String ds = format.format(new Date(System.currentTimeMillis()));
        return "上次更新:"+ds;
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/FragmentScoped.java
================================================
package com.halzhang.android.apps.startupnews.utils;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Scope;

/**
 * Created by Hal on 16/6/12.
 */
@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScoped {
}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/PreferenceUtils.java
================================================
/**
 * Copyright (C) 2013 HalZhang
 */

package com.halzhang.android.apps.startupnews.utils;

import com.halzhang.android.apps.startupnews.R;

import android.content.Context;
import android.preference.PreferenceManager;

/**
 * StartupNews
 * <p>
 * </p>
 * 
 * @author <a href="http://weibo.com/halzhang">Hal</a>
 * @version Mar 17, 2013
 */
public class PreferenceUtils {

    /**
     * 获取网页阅读模式
     * 
     * @param context
     * @return
     */
    public static String getHtmlProvider(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getString(
                context.getString(R.string.pref_key_html_provider),
                context.getString(R.string.default_html_provider));
    }

    /**
     * 使用内置浏览器
     * 
     * @param context
     * @return
     */
    public static boolean isUseInnerBrowse(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
                context.getString(R.string.pref_key_default_browse), true);
    }

    public static void set(Context ctx, final String key, final String value) {
        PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(key, value).commit();
    }

    public static String get(Context context, final String key) {
        return PreferenceManager.getDefaultSharedPreferences(context).getString(key, null);
    }

}


================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/UIUtils.java
================================================
package com.halzhang.android.apps.startupnews.utils;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.view.View;

/**
 * UI工具
 * Created by Hal on 13-5-26.
 */
public final class UIUtils {

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static void setActivatedCompat(View view, boolean activated) {
        if (hasHoneycomb()) {
            view.setActivated(activated);
        }
    }

    public static boolean isGoogleTV(Context context) {
        return context.getPackageManager().hasSystemFeature("com.google.android.tv");
    }

    public static boolean hasFroyo() {
        // Can use static final constants like FROYO, declared in later versions
        // of the OS since they are inlined at compile time. This is guaranteed behavior.
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
    }

    public static boolean hasGingerbread() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
    }

    public static boolean hasHoneycomb() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    public static boolean hasHoneycombMR1() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
    }

    public static boolean hasICS() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    }

    public static boolean hasJellyBean() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
    }

    public static boolean isTablet(Context context) {
        return (context.getResources().getConfiguration().screenLayout
                & Configuration.SCREENLAYOUT_SIZE_MASK)
                >= Configuration.SCREENLAYOUT_SIZE_LARGE;
    }

    public static boolean isHoneycombTablet(Context context) {
        return hasHoneycomb() && isTablet(context);
    }
}


================================================
FILE: app/src/main/res/anim/push_down_out.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     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.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
	<translate android:fromYDelta="0" android:toYDelta="100%p"
            android:duration="@android:integer/config_longAnimTime"/>
	<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
            android:duration="@android:integer/config_longAnimTime" />
</set>


================================================
FILE: app/src/main/res/anim/push_up_in.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     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.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
	<translate android:fromYDelta="100%p" android:toYDelta="0"
            android:duration="@android:integer/config_longAnimTime"/>
	<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_longAnimTime" />
</set>


================================================
FILE: app/src/main/res/anim/slide_in_left.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** 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.
*/
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
	<translate android:fromXDelta="-50%p" android:toXDelta="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
	<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>


================================================
FILE: app/src/main/res/anim/slide_in_right.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** 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.
*/
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
	<translate android:fromXDelta="50%p" android:toXDelta="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
	<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>


================================================
FILE: app/src/main/res/anim/slide_out_left.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** 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.
*/
-->

<set xmlns:android="http://schemas.android.com/apk/res/android">
	<translate android:fromXDelta="0" android:toXDelta="
Download .txt
gitextract_me0tpa6o/

├── .gitignore
├── .travis.yml
├── 3rdlib/
│   ├── commonlog/
│   │   ├── AndroidManifest.xml
│   │   ├── README.md
│   │   ├── build.gradle
│   │   ├── build.xml
│   │   ├── proguard-project.txt
│   │   ├── project.properties
│   │   └── src/
│   │       └── com/
│   │           └── halzhang/
│   │               └── android/
│   │                   └── common/
│   │                       └── CDLog.java
│   └── commontoast/
│       ├── AndroidManifest.xml
│       ├── README.md
│       ├── build.gradle
│       ├── build.xml
│       ├── proguard-project.txt
│       ├── project.properties
│       └── src/
│           └── com/
│               └── halzhang/
│                   └── android/
│                       └── common/
│                           └── CDToast.java
├── README.md
├── android-wait-for-emulator
├── app/
│   ├── build.gradle
│   ├── libs/
│   │   └── libGoogleAnalyticsServices.jar
│   ├── proguard-project.txt
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── halzhang/
│           │           └── android/
│           │               └── apps/
│           │                   └── startupnews/
│           │                       ├── ApplicationModule.java
│           │                       ├── Constants.java
│           │                       ├── MyApplication.java
│           │                       ├── SnApiComponent.java
│           │                       ├── analytics/
│           │                       │   ├── MyExceptionParser.java
│           │                       │   └── Tracker.java
│           │                       ├── presenter/
│           │                       │   ├── BasePresenter.java
│           │                       │   ├── BaseView.java
│           │                       │   ├── CommentsListContract.java
│           │                       │   ├── CommentsListPresenter.java
│           │                       │   ├── CommentsListPresenterModule.java
│           │                       │   ├── DiscussComponent.java
│           │                       │   ├── DiscussContract.java
│           │                       │   ├── DiscussPresenter.java
│           │                       │   ├── DiscussPresenterModule.java
│           │                       │   ├── LoginComponent.java
│           │                       │   ├── LoginContract.java
│           │                       │   ├── LoginPresenter.java
│           │                       │   ├── LoginPresenterModule.java
│           │                       │   ├── MainActivityContract.java
│           │                       │   ├── MainActivityPresenter.java
│           │                       │   ├── MainActivityPresenterModule.java
│           │                       │   ├── MainComponent.java
│           │                       │   ├── NewsListContract.java
│           │                       │   ├── NewsListFragmentComponent.java
│           │                       │   ├── NewsListPresenter.java
│           │                       │   └── NewsListPresenterModule.java
│           │                       ├── ui/
│           │                       │   ├── AboutActivity.java
│           │                       │   ├── BaseActivity.java
│           │                       │   ├── BaseFragmentActivity.java
│           │                       │   ├── DiscussActivity.java
│           │                       │   ├── LauncherActivity.java
│           │                       │   ├── LoginActivity.java
│           │                       │   ├── MainActivity.java
│           │                       │   ├── ShareHelper.java
│           │                       │   ├── SubmitActivity.java
│           │                       │   ├── fragment/
│           │                       │   │   ├── BaseFragment.java
│           │                       │   │   ├── CommentsListFragment.java
│           │                       │   │   ├── LoginFragment.java
│           │                       │   │   ├── NewsListFragment.java
│           │                       │   │   └── SwipeRefreshRecyclerFragment.java
│           │                       │   ├── phone/
│           │                       │   │   └── BrowseActivity.java
│           │                       │   ├── tablet/
│           │                       │   │   ├── BrowseFragment.java
│           │                       │   │   └── DiscussFragment.java
│           │                       │   └── widgets/
│           │                       │       ├── CardViewDividerDecoration.java
│           │                       │       ├── DividerDecoration.java
│           │                       │       ├── ObservableWebView.java
│           │                       │       ├── SwitchPreference.java
│           │                       │       └── WebViewController.java
│           │                       └── utils/
│           │                           ├── ActivityScoped.java
│           │                           ├── ActivityUtils.java
│           │                           ├── AppUtils.java
│           │                           ├── CrashHandler.java
│           │                           ├── CustomTabsActivityHelper.java
│           │                           ├── CustomTabsHelper.java
│           │                           ├── DateUtils.java
│           │                           ├── FragmentScoped.java
│           │                           ├── PreferenceUtils.java
│           │                           └── UIUtils.java
│           └── res/
│               ├── anim/
│               │   ├── push_down_out.xml
│               │   ├── push_up_in.xml
│               │   ├── slide_in_left.xml
│               │   ├── slide_in_right.xml
│               │   ├── slide_out_left.xml
│               │   └── slide_out_right.xml
│               ├── drawable/
│               │   ├── action_bar_background.xml
│               │   ├── background_boardless.xml
│               │   ├── background_holo_dark.xml
│               │   ├── background_holo_light.xml
│               │   ├── bg_discuss_article.xml
│               │   ├── fragment_shadow.xml
│               │   ├── send_button_selector.xml
│               │   └── sidebar_shadow.xml
│               ├── layout/
│               │   ├── actionbar_indeterminate_progress.xml
│               │   ├── activity_browse.xml
│               │   ├── activity_discuss.xml
│               │   ├── activity_login.xml
│               │   ├── activity_main.xml
│               │   ├── browse_bar.xml
│               │   ├── comment_list_item.xml
│               │   ├── discuss_comment_item.xml
│               │   ├── discuss_header_view.xml
│               │   ├── fragment_browse.xml
│               │   ├── fragment_discuss.xml
│               │   ├── fragment_login.xml
│               │   ├── news_list_item.xml
│               │   ├── preference_toolbar.xml
│               │   ├── ptr_list_layout.xml
│               │   └── refresh_recycler_view_layout.xml
│               ├── menu/
│               │   ├── activity_browse.xml
│               │   ├── activity_login.xml
│               │   ├── activity_main.xml
│               │   ├── fragment_browse.xml
│               │   ├── fragment_discuss.xml
│               │   └── fragment_news.xml
│               ├── values/
│               │   ├── analytics.xml
│               │   ├── colors.xml
│               │   ├── dimens.xml
│               │   ├── ids.xml
│               │   ├── integers.xml
│               │   ├── strings.xml
│               │   └── themes.xml
│               └── xml/
│                   └── preferences.xml
├── build.gradle
├── buildsystem/
│   └── dependencies.gradle
├── changelog.md
├── data/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── halzhang/
│       │               └── android/
│       │                   └── startupnews/
│       │                       └── data/
│       │                           └── ApplicationTest.java
│       └── main/
│           ├── AndroidManifest.xml
│           ├── java/
│           │   └── com/
│           │       └── halzhang/
│           │           └── android/
│           │               └── startupnews/
│           │                   └── data/
│           │                       ├── Constant.java
│           │                       ├── CookieFactoryModule.java
│           │                       ├── JsoupConnectorModule.java
│           │                       ├── OkHttpClientModule.java
│           │                       ├── SessionManagerModule.java
│           │                       ├── SnApiModule.java
│           │                       ├── entity/
│           │                       │   ├── SNComment.java
│           │                       │   ├── SNComments.java
│           │                       │   ├── SNDiscuss.java
│           │                       │   ├── SNFeed.java
│           │                       │   ├── SNNew.java
│           │                       │   ├── SNSession.java
│           │                       │   ├── SNUser.java
│           │                       │   └── Status.java
│           │                       ├── exception/
│           │                       │   ├── LoginException.java
│           │                       │   ├── NetworkException.java
│           │                       │   └── SessionExpiredException.java
│           │                       ├── net/
│           │                       │   ├── ISnApi.java
│           │                       │   ├── JsoupConnector.java
│           │                       │   └── SnApiImpl.java
│           │                       ├── parser/
│           │                       │   ├── BaseHTMLParser.java
│           │                       │   ├── SNCommentsParser.java
│           │                       │   ├── SNCommentsParserV1.java
│           │                       │   ├── SNDiscussParser.java
│           │                       │   └── SNFeedParser.java
│           │                       └── utils/
│           │                           ├── CookieFactoryImpl.java
│           │                           ├── NetworkUtils.java
│           │                           ├── OkHttpClientHelper.java
│           │                           ├── PersistentCookieStore.java
│           │                           ├── PrefUtils.java
│           │                           ├── SerializableCookie.java
│           │                           └── SessionManager.java
│           └── res/
│               └── values/
│                   └── strings.xml
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release/
│   ├── StartupNews-release-0.1.apk
│   ├── StartupNews-release-0.2.0.0.apk
│   ├── StartupNews-release-0.3.0.0.apk
│   ├── StartupNews-release-0.3.1.0.apk
│   ├── StartupNews-release-0.4.0.0.apk
│   ├── StartupNews-release-0.5.0.0.apk
│   ├── StartupNews-release-0.5.1.0.apk
│   ├── StartupNews-release-0.5.2.0.apk
│   ├── StartupNews-release-0.6.0.0.apk
│   ├── StartupNews-release-1.0.0.0.apk
│   ├── StartupNews-release-1.0.1.0.apk
│   └── StartupNews-release.apk
├── settings.gradle
└── wait_for_emulator.sh
Download .txt
SYMBOL INDEX (654 symbols across 92 files)

FILE: 3rdlib/commonlog/src/com/halzhang/android/common/CDLog.java
  class CDLog (line 13) | public class CDLog {
    method makeLogTag (line 24) | public static String makeLogTag(String str) {
    method makeLogTag (line 34) | public static String makeLogTag(Class<?> cls) {
    method i (line 38) | public static void i(String tag, String msg, Throwable throwable) {
    method i (line 44) | public static void i(String tag, Object... objects) {
    method i (line 52) | public static void i(String tag, String msg) {
    method i (line 56) | public static void i(String msg) {
    method d (line 60) | public static void d(String tag, String msg, Throwable throwable) {
    method d (line 66) | public static void d(String tag, String msg) {
    method d (line 70) | public static void d(String msg) {
    method w (line 74) | public static void w(String tag, String msg, Throwable throwable) {
    method w (line 80) | public static void w(String tag, String msg) {
    method w (line 84) | public static void w(String msg) {
    method e (line 88) | public static void e(String tag, String msg, Throwable throwable) {
    method e (line 94) | public static void e(String tag, String msg) {
    method e (line 98) | public static void e(String msg) {
    method CDLog (line 102) | private CDLog() {

FILE: 3rdlib/commontoast/src/com/halzhang/android/common/CDToast.java
  class CDToast (line 15) | public class CDToast {
    method showToast (line 25) | public static void showToast(Context context, String text) {
    method showToast (line 45) | public static void showToast(Context context, int resId) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ApplicationModule.java
  class ApplicationModule (line 13) | @Module
    method ApplicationModule (line 19) | public ApplicationModule(Context context) {
    method provideContext (line 23) | @Provides

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/Constants.java
  class Constants (line 10) | public abstract class Constants {
    method Constants (line 15) | protected Constants() {
    class IntentAction (line 34) | public final class IntentAction {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/MyApplication.java
  class MyApplication (line 41) | public class MyApplication extends Application {
    method newThread (line 50) | public Thread newThread(Runnable r) {
    method instance (line 59) | public static MyApplication instance() {
    method MyApplication (line 63) | public MyApplication() {
    method onCreate (line 70) | @Override
    method getSnApiComponent (line 100) | public SnApiComponent getSnApiComponent() {
    method initHistory (line 104) | private void initHistory() {
    method storeHistory (line 133) | private void storeHistory() {
    method addHistory (line 171) | public void addHistory(String url) {
    method clearHistory (line 187) | public void clearHistory() {
    method isHistoryContains (line 191) | public boolean isHistoryContains(String url) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/SnApiComponent.java
  type SnApiComponent (line 19) | @Singleton
    method getSnApi (line 24) | ISnApi getSnApi();
    method getSessionManager (line 26) | SessionManager getSessionManager();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/analytics/MyExceptionParser.java
  class MyExceptionParser (line 31) | public class MyExceptionParser implements ExceptionParser {
    method MyExceptionParser (line 37) | public MyExceptionParser(Context context) {
    method getDescription (line 41) | @Override
    method getStackTraceString (line 54) | public static String getStackTraceString(Throwable tr) {
    method collectDeviceInfo (line 71) | public void collectDeviceInfo(Context ctx) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/analytics/Tracker.java
  class Tracker (line 14) | public class Tracker {
    method Tracker (line 20) | private Tracker() {
    class InstanceHolder (line 24) | private static class InstanceHolder {
    method getInstance (line 28) | public static Tracker getInstance() {
    method init (line 32) | public void init(Context context) {
    method sendException (line 36) | public void sendException(String message, Throwable e, boolean fatal) {
    method sendEvent (line 44) | public void sendEvent(String category, String action, String label, Lo...

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BasePresenter.java
  type BasePresenter (line 6) | public interface BasePresenter {
    method start (line 8) | void start();
    method stop (line 9) | void stop();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BaseView.java
  type BaseView (line 6) | public interface BaseView<T> {
    method setPresenter (line 8) | void setPresenter(T presenter);
    method isActive (line 10) | boolean isActive();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListContract.java
  type CommentsListContract (line 10) | public interface CommentsListContract {
    type Presenter (line 12) | interface Presenter extends BasePresenter {
      method getComments (line 13) | void getComments(String url);
      method getMoreComments (line 15) | void getMoreComments();
    type View (line 18) | interface View extends BaseView<Presenter> {
      method onSuccess (line 19) | void onSuccess(ArrayList<SNComment> snComments);
      method onFailure (line 21) | void onFailure(Throwable e);
      method onAtEnd (line 23) | void onAtEnd();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenter.java
  class CommentsListPresenter (line 18) | public class CommentsListPresenter implements CommentsListContract.Prese...
    method CommentsListPresenter (line 27) | @Inject
    method setupListener (line 33) | @Inject
    method getComments (line 38) | @Override
    method getMoreComments (line 68) | @Override
    method start (line 110) | @Override
    method stop (line 114) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenterModule.java
  class CommentsListPresenterModule (line 10) | @Module
    method CommentsListPresenterModule (line 15) | public CommentsListPresenterModule(CommentsListContract.View view) {
    method provideCommentsListContractView (line 19) | @Provides

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussComponent.java
  type DiscussComponent (line 12) | @ActivityScoped
    method inject (line 16) | void inject(DiscussActivity discussActivity);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussContract.java
  type DiscussContract (line 9) | public interface DiscussContract {
    type Presenter (line 11) | interface Presenter extends BasePresenter {
      method getDiscuss (line 12) | void getDiscuss(String url);
      method comment (line 14) | void comment(String message);
    type View (line 17) | interface View extends BaseView<Presenter> {
      method onGetDiscuss (line 18) | void onGetDiscuss(SNDiscuss snDiscuss);
      method onGetDiscussFailure (line 20) | void onGetDiscussFailure(Throwable e);
      method onCommentSuccess (line 22) | void onCommentSuccess(Status status);
      method onCommentFailure (line 24) | void onCommentFailure(Throwable e);
      method onSessionExpired (line 26) | void onSessionExpired();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenter.java
  class DiscussPresenter (line 19) | public class DiscussPresenter implements DiscussContract.Presenter {
    method DiscussPresenter (line 32) | @Inject
    method setupListener (line 39) | @Inject
    method start (line 44) | @Override
    method stop (line 49) | @Override
    method getDiscuss (line 54) | @Override
    method comment (line 84) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenterModule.java
  class DiscussPresenterModule (line 9) | @Module
    method DiscussPresenterModule (line 14) | public DiscussPresenterModule(DiscussContract.View view) {
    method provideDiscussContractView (line 18) | @Provides

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginComponent.java
  type LoginComponent (line 14) | @ActivityScoped
    method inject (line 18) | void inject(LoginActivity loginActivity);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginContract.java
  type LoginContract (line 9) | public interface LoginContract {
    type Presenter (line 11) | interface Presenter extends BasePresenter {
      method login (line 12) | void login(String username, String password);
    type View (line 15) | interface View extends BaseView<Presenter> {
      method onLoginError (line 17) | void onLoginError(Throwable e);
      method onLoginResult (line 19) | void onLoginResult(String user);
      method addSubscription (line 21) | void addSubscription(Subscription subscription);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenter.java
  class LoginPresenter (line 19) | public class LoginPresenter implements LoginContract.Presenter {
    method LoginPresenter (line 27) | @Inject
    method setupListener (line 33) | @Inject
    method login (line 38) | @Override
    method start (line 74) | @Override
    method stop (line 79) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenterModule.java
  class LoginPresenterModule (line 10) | @Module
    method LoginPresenterModule (line 15) | public LoginPresenterModule(LoginContract.View view) {
    method provideLoginContractView (line 19) | @Provides

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityContract.java
  type MainActivityContract (line 8) | public interface MainActivityContract {
    type Presenter (line 10) | interface Presenter extends BasePresenter {
      method logout (line 11) | void logout();
      method upVote (line 18) | void upVote(String postId);
    type View (line 21) | interface View extends BaseView<Presenter> {
      method onLogoutResult (line 28) | void onLogoutResult(boolean result);
      method onUpVoteFailure (line 30) | void onUpVoteFailure(Throwable e);
      method onUpVoteSuccess (line 32) | void onUpVoteSuccess(Status status);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenter.java
  class MainActivityPresenter (line 17) | public class MainActivityPresenter implements MainActivityContract.Prese...
    method MainActivityPresenter (line 24) | @Inject
    method setupListener (line 30) | @Inject
    method logout (line 35) | @Override
    method upVote (line 59) | @Override
    method start (line 86) | @Override
    method stop (line 91) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenterModule.java
  class MainActivityPresenterModule (line 9) | @Module
    method MainActivityPresenterModule (line 14) | public MainActivityPresenterModule(MainActivityContract.View view) {
    method provideMainActivityContractView (line 18) | @Provides

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainComponent.java
  type MainComponent (line 13) | @ActivityScoped
    method inject (line 18) | void inject(MainActivity mainActivity);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListContract.java
  type NewsListContract (line 10) | public interface NewsListContract {
    type Presenter (line 12) | interface Presenter extends BasePresenter {
      method getFeed (line 13) | void getFeed(String url);
      method getMoreFeed (line 15) | void getMoreFeed();
    type View (line 18) | interface View extends BaseView<NewsListContract.Presenter> {
      method onSuccess (line 19) | void onSuccess(ArrayList<SNNew> snNews);
      method onFailure (line 21) | void onFailure(Throwable e);
      method onAtEnd (line 23) | void onAtEnd();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListFragmentComponent.java
  type NewsListFragmentComponent (line 12) | @FragmentScoped
    method inject (line 16) | void inject(NewsListFragment newsListFragment);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenter.java
  class NewsListPresenter (line 19) | public class NewsListPresenter implements NewsListContract.Presenter {
    method NewsListPresenter (line 29) | @Inject
    method setupListener (line 35) | @Inject
    method start (line 40) | @Override
    method stop (line 45) | @Override
    method getFeed (line 50) | @Override
    method getMoreFeed (line 80) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenterModule.java
  class NewsListPresenterModule (line 10) | @Module
    method NewsListPresenterModule (line 15) | public NewsListPresenterModule(NewsListContract.View view) {
    method provideNewsListContractView (line 19) | @Provides

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/AboutActivity.java
  class AboutActivity (line 29) | public class AboutActivity extends BaseFragmentActivity {
    method onCreate (line 31) | @Override
    method onPostCreate (line 42) | @Override
    method onOptionsItemSelected (line 57) | @Override
    class AppPreferenceFragment (line 70) | public static class AppPreferenceFragment extends PreferenceFragment i...
      method onCreate (line 71) | @Override
      method onPreferenceChange (line 85) | @Override
    method onStart (line 105) | @Override
    method onStop (line 111) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseActivity.java
  class BaseActivity (line 8) | public class BaseActivity extends AppCompatActivity {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseFragmentActivity.java
  class BaseFragmentActivity (line 25) | public class BaseFragmentActivity extends AppCompatActivity {
    method onCreate (line 27) | @Override
    method onStart (line 37) | @Override
    method onStop (line 43) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/DiscussActivity.java
  class DiscussActivity (line 38) | public class DiscussActivity extends BaseFragmentActivity {
    method start (line 46) | public static void start(Context context, String discussUrl, SNNew snN...
    method onReceive (line 60) | @Override
    method onCreate (line 74) | @Override
    method onStart (line 97) | @Override
    method onResume (line 102) | @Override
    method onDestroy (line 108) | @Override
    method onCreateOptionsMenu (line 114) | @Override
    method onOptionsItemSelected (line 120) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/LauncherActivity.java
  class LauncherActivity (line 6) | public class LauncherActivity extends BaseFragmentActivity {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/LoginActivity.java
  class LoginActivity (line 34) | public class LoginActivity extends BaseActivity {
    method onCreate (line 43) | @Override
    method onOptionsItemSelected (line 58) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/MainActivity.java
  class MainActivity (line 53) | public class MainActivity extends BaseFragmentActivity implements OnNews...
    method onCreate (line 85) | @Override
    method onStart (line 105) | @Override
    method onStop (line 111) | @Override
    method onPostCreate (line 117) | @Override
    method setupViews (line 122) | private void setupViews() {
    method createEmailIntent (line 143) | private Intent createEmailIntent() {
    method onCreateOptionsMenu (line 157) | @Override
    method onPrepareOptionsMenu (line 163) | @Override
    method onOptionsItemSelected (line 175) | @Override
    method showDiscussFragment (line 215) | private void showDiscussFragment() {
    method showDiscussFragment (line 219) | private void showDiscussFragment(SNNew snNew, String discussUrl) {
    method onConfigurationChanged (line 234) | @Override
    method onLogoutResult (line 239) | @Override
    method onUpVoteFailure (line 245) | @Override
    method onUpVoteSuccess (line 251) | @Override
    method setPresenter (line 270) | @Override
    method isActive (line 275) | @Override
    class SectionsPagerAdapter (line 280) | private class SectionsPagerAdapter extends FragmentPagerAdapter {
      method SectionsPagerAdapter (line 284) | public SectionsPagerAdapter(FragmentManager fm) {
      method getItem (line 289) | @Override
      method getCount (line 303) | @Override
      method getPageTitle (line 308) | @Override
      method getItemId (line 313) | @Override
    method onNewsSelected (line 320) | @Override
    method showBrowseFragment (line 335) | private void showBrowseFragment(SNNew snNew) {
    method onShowArticleSelected (line 354) | @Override
    method onUpVoteSelected (line 360) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/ShareHelper.java
  class ShareHelper (line 16) | public class ShareHelper {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/SubmitActivity.java
  class SubmitActivity (line 15) | public class SubmitActivity extends FragmentActivity {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/BaseFragment.java
  class BaseFragment (line 12) | public class BaseFragment extends Fragment {
    method onCreate (line 16) | @Override
    method onDestroyView (line 22) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/CommentsListFragment.java
  class CommentsListFragment (line 36) | public class CommentsListFragment extends SwipeRefreshRecyclerFragment i...
    method CommentsListFragment (line 51) | public CommentsListFragment() {
    method newInstance (line 54) | public static CommentsListFragment newInstance() {
    method onCreate (line 58) | @Override
    method onAttach (line 64) | @Override
    method onActivityCreated (line 69) | @Override
    method onDestroyView (line 78) | @Override
    method onDestroy (line 83) | @Override
    method onRefreshData (line 88) | @Override
    method onLoadMore (line 97) | @Override
    method onSuccess (line 105) | @Override
    method onFailure (line 112) | @Override
    method onAtEnd (line 120) | @Override
    method setPresenter (line 126) | @Override
    method isActive (line 131) | @Override
    class CommentsAdapter (line 136) | private class CommentsAdapter extends RecyclerView.Adapter<CommentsAda...
      class ViewHolder (line 138) | public final class ViewHolder extends RecyclerView.ViewHolder {
        method ViewHolder (line 150) | public ViewHolder(View itemView) {
      method onCreateViewHolder (line 160) | @Override
      method onBindViewHolder (line 165) | @Override
      method getItemId (line 186) | @Override
      method getItemCount (line 191) | @Override
      method isEmpty (line 196) | public boolean isEmpty() {
    method getViewLayout (line 201) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/LoginFragment.java
  class LoginFragment (line 35) | public class LoginFragment extends BaseFragment implements LoginContract...
    method LoginFragment (line 50) | public LoginFragment() {
    method newInstance (line 54) | public static LoginFragment newInstance() {
    method onCreate (line 58) | @Override
    method onAttach (line 63) | @Override
    method onCreateView (line 68) | @Override
    method attemptLogin (line 104) | public void attemptLogin() {
    method showProgress (line 138) | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    method onLoginError (line 172) | @Override
    method onLoginResult (line 177) | @Override
    method addSubscription (line 195) | @Override
    method setPresenter (line 200) | @Override
    method isActive (line 205) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/NewsListFragment.java
  class NewsListFragment (line 54) | public class NewsListFragment extends SwipeRefreshRecyclerFragment imple...
    method setPresenter (line 63) | @Override
    method isActive (line 68) | @Override
    method onSuccess (line 73) | @Override
    method onFailure (line 80) | @Override
    method onAtEnd (line 88) | @Override
    type OnNewsSelectedListener (line 97) | public interface OnNewsSelectedListener {
      method onNewsSelected (line 104) | public void onNewsSelected(int position, SNNew snNew);
    method NewsListFragment (line 118) | public NewsListFragment() {
    method newInstance (line 122) | public static NewsListFragment newInstance(String url) {
    method onReceive (line 132) | @Override
    method onAttach (line 144) | @Override
    method onDetach (line 155) | @Override
    method onCreate (line 161) | @Override
    method getViewLayout (line 179) | @Override
    method onActivityCreated (line 184) | @Override
    method onDestroyView (line 199) | @Override
    method onDestroy (line 205) | @Override
    method onContextItemSelected (line 211) | @Override
    method onRefreshData (line 240) | @Override
    method onLoadMore (line 247) | @Override
    method openArticle (line 255) | private void openArticle(int position, SNNew snNew) {
    method openDiscuss (line 261) | private void openDiscuss(SNNew snNew) {
    class NewsAdapter (line 269) | private class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.Vie...
      class ViewHolder (line 271) | public final class ViewHolder extends RecyclerView.ViewHolder implem...
        method ViewHolder (line 285) | public ViewHolder(View itemView) {
        method onCreateContextMenu (line 307) | @Override
      method onCreateViewHolder (line 314) | @Override
      method onBindViewHolder (line 319) | @Override
      method getItemCount (line 337) | @Override
      method isEmpty (line 342) | public boolean isEmpty() {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/SwipeRefreshRecyclerFragment.java
  class SwipeRefreshRecyclerFragment (line 18) | public abstract class SwipeRefreshRecyclerFragment extends Fragment {
    method SwipeRefreshRecyclerFragment (line 27) | public SwipeRefreshRecyclerFragment() {
    method onCreate (line 31) | @Override
    method getViewLayout (line 36) | protected abstract int getViewLayout();
    method onCreateView (line 38) | @Override
    method onRefreshData (line 88) | protected void onRefreshData() {
    method onLoadMore (line 94) | protected void onLoadMore() {
    method onRefreshComplete (line 97) | protected void onRefreshComplete() {
    method onAttach (line 103) | @Override
    method onDetach (line 108) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/phone/BrowseActivity.java
  class BrowseActivity (line 28) | public class BrowseActivity extends BaseFragmentActivity {
    method onCreate (line 37) | @Override
    method onOptionsItemSelected (line 63) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/BrowseFragment.java
  class BrowseFragment (line 33) | public class BrowseFragment extends Fragment implements OnScrollChangedC...
    method onAttach (line 45) | @Override
    method onCreate (line 50) | @Override
    method onCreateView (line 56) | @Override
    method onDestroy (line 77) | @Override
    method setTitle (line 83) | public void setTitle(String title) {
    method load (line 87) | public void load(String url) {
    method onCreateOptionsMenu (line 93) | @Override
    method createShareIntent (line 133) | private Intent createShareIntent() {
    method getShareContent (line 141) | private String getShareContent() {
    method onOptionsItemSelected (line 146) | @Override
    method onScroll (line 158) | @Override
    method onScrollUp (line 163) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    method onScrollDown (line 172) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/DiscussFragment.java
  class DiscussFragment (line 45) | public class DiscussFragment extends Fragment implements OnItemClickList...
    method setPresenter (line 55) | @Override
    method isActive (line 60) | @Override
    method onGetDiscuss (line 65) | @Override
    method onGetDiscussFailure (line 74) | @Override
    method onCommentSuccess (line 80) | @Override
    method onCommentFailure (line 106) | @Override
    method onSessionExpired (line 112) | @Override
    type OnMenuSelectedListener (line 118) | public interface OnMenuSelectedListener {
      method onShowArticleSelected (line 119) | public void onShowArticleSelected(SNNew snNew);
      method onUpVoteSelected (line 121) | public void onUpVoteSelected(String postId);
    method DiscussFragment (line 149) | public DiscussFragment() {
    method newInstance (line 153) | public static DiscussFragment newInstance(String discussURL, SNNew snN...
    method onAttach (line 162) | @Override
    method onCreateView (line 170) | @Override
    method onClick (line 224) | @Override
    method onDestroy (line 232) | @Override
    method onCreateOptionsMenu (line 237) | @Override
    method wrapHeaderView (line 244) | private void wrapHeaderView(SNNew snNew) {
    method setRefreshActionButtonState (line 257) | public void setRefreshActionButtonState(boolean refreshing) {
    method loadData (line 273) | public void loadData() {
    method onOptionsItemSelected (line 278) | @Override
    class DiscussCommentAdapter (line 300) | private class DiscussCommentAdapter extends BaseAdapter {
      method getCount (line 302) | @Override
      method getItem (line 307) | @Override
      method getItemId (line 312) | @Override
      method getView (line 317) | @Override
      class ViewHolder (line 344) | class ViewHolder {
    method onItemClick (line 356) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/CardViewDividerDecoration.java
  class CardViewDividerDecoration (line 18) | public class CardViewDividerDecoration extends RecyclerView.ItemDecorati...
    method CardViewDividerDecoration (line 23) | public CardViewDividerDecoration(Context context) {
    method onDrawOver (line 28) | @Override
    method drawVertical (line 36) | public void drawVertical(Canvas c, RecyclerView parent) {
    method getItemOffsets (line 52) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/DividerDecoration.java
  class DividerDecoration (line 17) | public class DividerDecoration extends RecyclerView.ItemDecoration {
    method DividerDecoration (line 24) | public DividerDecoration(Context context) {
    method onDrawOver (line 32) | @Override
    method drawVertical (line 40) | public void drawVertical(Canvas c, RecyclerView parent) {
    method getItemOffsets (line 56) | @Override

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/ObservableWebView.java
  class ObservableWebView (line 21) | public class ObservableWebView extends WebView {
    method ObservableWebView (line 33) | public ObservableWebView(final Context context) {
    method ObservableWebView (line 37) | public ObservableWebView(final Context context, final AttributeSet att...
    method ObservableWebView (line 41) | public ObservableWebView(final Context context, final AttributeSet att...
    method onScrollChanged (line 45) | @Override
    method getOnScrollChangedCallback (line 63) | public OnScrollChangedCallback getOnScrollChangedCallback() {
    method setOnScrollChangedCallback (line 67) | public void setOnScrollChangedCallback(final OnScrollChangedCallback o...
    type OnScrollChangedCallback (line 75) | public static interface OnScrollChangedCallback {
      method onScroll (line 76) | public void onScroll(int l, int t, int oldl, int oldt);
      method onScrollUp (line 81) | public void onScrollUp();
      method onScrollDown (line 86) | public void onScrollDown();

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/SwitchPreference.java
  class SwitchPreference (line 17) | public class SwitchPreference extends TwoStatePreference {
    method SwitchPreference (line 19) | public SwitchPreference(Context context, AttributeSet attrs) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/WebViewController.java
  class WebViewController (line 35) | public class WebViewController implements OnClickListener {
    method WebViewController (line 51) | public WebViewController(Activity activity) {
    method initControllerView (line 55) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    class MyWebViewClient (line 89) | private class MyWebViewClient extends WebViewClient {
      method shouldOverrideUrlLoading (line 90) | @Override
      method onPageStarted (line 96) | @Override
      method onPageFinished (line 102) | @Override
    method setCurrentUrl (line 109) | private void setCurrentUrl(String url) {
    method getCurrentUrl (line 113) | private String getCurrentUrl() {
    class MyWebChromeClient (line 117) | private class MyWebChromeClient extends WebChromeClient {
      method onProgressChanged (line 119) | @Override
      method onReceivedTitle (line 129) | @Override
    method onClick (line 137) | @Override
    method back (line 161) | private void back() {
    method forward (line 169) | private void forward() {
    method readability (line 177) | private void readability() {
    method refresh (line 186) | private void refresh() {
    method webSite (line 192) | private void webSite() {
    method getWebView (line 204) | public WebView getWebView() {
    method destroy (line 208) | public void destroy() {
    method loadUrl (line 221) | public void loadUrl(String url) {
    method showBrowseBar (line 229) | public void showBrowseBar() {
    method hideBrowseBar (line 237) | public void hideBrowseBar() {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityUtils.java
  class ActivityUtils (line 33) | public class ActivityUtils {
    method openArticle (line 35) | public static void openArticle(Activity activity, SNNew snNew) {
    method openActicle (line 59) | public static void openActicle(Activity activity, SNNew snNew, View v) {
    method isIntentAvailable (line 96) | public static boolean isIntentAvailable(Context context, Intent intent) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/AppUtils.java
  class AppUtils (line 21) | public class AppUtils {
    method getVersionName (line 23) | public static String getVersionName(Context context) {
    method getVersionCode (line 34) | public static int getVersionCode(Context context) {
    method getMyApplication (line 45) | public static MyApplication getMyApplication(Context context) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/CrashHandler.java
  class CrashHandler (line 36) | public class CrashHandler implements UncaughtExceptionHandler {
    method CrashHandler (line 48) | private CrashHandler() {
    method getInstance (line 51) | public static CrashHandler getInstance() {
    method init (line 58) | public void init(Context context) {
    method uncaughtException (line 63) | @Override
    method handleException (line 74) | private boolean handleException(Throwable ex) {
    method collectDeviceInfo (line 83) | private void collectDeviceInfo(Context ctx) {
    method saveCrashInfo2File (line 108) | private String saveCrashInfo2File(Throwable ex) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsActivityHelper.java
  class CustomTabsActivityHelper (line 20) | public class CustomTabsActivityHelper {
    method bindService (line 29) | public void bindService(Context context) {
    method getSession (line 56) | public CustomTabsSession getSession() {
    method unBindService (line 66) | public void unBindService(Context context) {
    method launchUrl (line 82) | public void launchUrl(Activity activity, String url, CustomTabsIntent ...
    type OnCustomTabsInvalidListener (line 100) | public interface OnCustomTabsInvalidListener {
      method onInvalid (line 106) | public void onInvalid(String url);

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsHelper.java
  class CustomTabsHelper (line 20) | public class CustomTabsHelper {
    method CustomTabsHelper (line 32) | private CustomTabsHelper() {
    method getPackageNameToUse (line 45) | public static String getPackageNameToUse(Context context) {
    method hasSpecializedHandlerIntents (line 97) | private static boolean hasSpecializedHandlerIntents(Context context, I...
    method getPackages (line 122) | public static String[] getPackages() {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/DateUtils.java
  class DateUtils (line 18) | public class DateUtils {
    method getLastUpdateLabel (line 20) | public static String getLastUpdateLabel(Context context){

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/PreferenceUtils.java
  class PreferenceUtils (line 20) | public class PreferenceUtils {
    method getHtmlProvider (line 28) | public static String getHtmlProvider(Context context) {
    method isUseInnerBrowse (line 40) | public static boolean isUseInnerBrowse(Context context) {
    method set (line 45) | public static void set(Context ctx, final String key, final String val...
    method get (line 49) | public static String get(Context context, final String key) {

FILE: app/src/main/java/com/halzhang/android/apps/startupnews/utils/UIUtils.java
  class UIUtils (line 13) | public final class UIUtils {
    method setActivatedCompat (line 15) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    method isGoogleTV (line 22) | public static boolean isGoogleTV(Context context) {
    method hasFroyo (line 26) | public static boolean hasFroyo() {
    method hasGingerbread (line 32) | public static boolean hasGingerbread() {
    method hasHoneycomb (line 36) | public static boolean hasHoneycomb() {
    method hasHoneycombMR1 (line 40) | public static boolean hasHoneycombMR1() {
    method hasICS (line 44) | public static boolean hasICS() {
    method hasJellyBean (line 48) | public static boolean hasJellyBean() {
    method isTablet (line 52) | public static boolean isTablet(Context context) {
    method isHoneycombTablet (line 58) | public static boolean isHoneycombTablet(Context context) {

FILE: data/src/androidTest/java/com/halzhang/android/startupnews/data/ApplicationTest.java
  class ApplicationTest (line 9) | public class ApplicationTest extends ApplicationTestCase<Application> {
    method ApplicationTest (line 10) | public ApplicationTest() {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/Constant.java
  class Constant (line 7) | public class Constant {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/CookieFactoryModule.java
  class CookieFactoryModule (line 15) | @Module
    method provideCookieFactory (line 18) | @Singleton

FILE: data/src/main/java/com/halzhang/android/startupnews/data/JsoupConnectorModule.java
  class JsoupConnectorModule (line 16) | @Module
    method provideJsoupConnector (line 19) | @Singleton

FILE: data/src/main/java/com/halzhang/android/startupnews/data/OkHttpClientModule.java
  class OkHttpClientModule (line 16) | @Module
    method provideOkHttpClient (line 19) | @Singleton

FILE: data/src/main/java/com/halzhang/android/startupnews/data/SessionManagerModule.java
  class SessionManagerModule (line 16) | @Module
    method provideSessionManager (line 19) | @Singleton

FILE: data/src/main/java/com/halzhang/android/startupnews/data/SnApiModule.java
  class SnApiModule (line 19) | @Module
    method provideSnApi (line 22) | @Singleton

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNComment.java
  class SNComment (line 18) | public class SNComment implements Serializable {
    method SNComment (line 43) | public SNComment(){}
    method SNComment (line 45) | public SNComment(String linkURL, String parentURL, String discussURL, ...
    method getLinkURL (line 59) | public String getLinkURL() {
    method setLinkURL (line 63) | public void setLinkURL(String linkURL) {
    method getParentURL (line 67) | public String getParentURL() {
    method setParentURL (line 71) | public void setParentURL(String parentURL) {
    method getDiscussURL (line 75) | public String getDiscussURL() {
    method setDiscussURL (line 79) | public void setDiscussURL(String discussURL) {
    method getText (line 83) | public String getText() {
    method setText (line 87) | public void setText(String text) {
    method getUser (line 91) | public SNUser getUser() {
    method setUser (line 95) | public void setUser(SNUser user) {
    method getCreated (line 99) | public String getCreated() {
    method setCreated (line 103) | public void setCreated(String created) {
    method getArtistTitle (line 107) | public String getArtistTitle() {
    method setArtistTitle (line 111) | public void setArtistTitle(String artistTitle) {
    method getVoteURL (line 115) | public String getVoteURL() {
    method setVoteURL (line 119) | public void setVoteURL(String voteURL) {
    method getReplayURL (line 123) | public String getReplayURL() {
    method setReplayURL (line 127) | public void setReplayURL(String replayURL) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNComments.java
  class SNComments (line 18) | public class SNComments implements Serializable {
    method getSnComments (line 29) | public ArrayList<SNComment> getSnComments() {
    method setSnComments (line 33) | public void setSnComments(ArrayList<SNComment> snComments) {
    method getMoreURL (line 37) | public String getMoreURL() {
    method setMoreURL (line 41) | public void setMoreURL(String moreURL) {
    method clear (line 45) | public void clear(){
    method addComment (line 49) | public void addComment(SNComment comment){
    method addComments (line 55) | public void addComments(ArrayList<SNComment> comments){
    method size (line 61) | public int size(){

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNDiscuss.java
  class SNDiscuss (line 18) | public class SNDiscuss implements Serializable {
    method getSnNew (line 27) | public SNNew getSnNew() {
    method setSnNew (line 31) | public void setSnNew(SNNew snNew) {
    method getComments (line 35) | public ArrayList<SNComment> getComments() {
    method setComments (line 39) | public void setComments(ArrayList<SNComment> comments) {
    method getFnid (line 43) | public String getFnid() {
    method setFnid (line 47) | public void setFnid(String fnid) {
    method commentSize (line 51) | public int commentSize() {
    method addComments (line 55) | public void addComments(ArrayList<SNComment> comments) {
    method addComment (line 61) | public void addComment(SNComment comment) {
    method clearComments (line 67) | public void clearComments(){
    method copy (line 76) | public void copy(SNDiscuss discuss) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNFeed.java
  class SNFeed (line 21) | public class SNFeed implements Parcelable {
    method getSnNews (line 29) | public ArrayList<SNNew> getSnNews() {
    method setSnNews (line 33) | public void setSnNews(ArrayList<SNNew> mSnNews) {
    method getMoreUrl (line 37) | public String getMoreUrl() {
    method setMoreUrl (line 41) | public void setMoreUrl(String mMoreUrl) {
    method addNew (line 45) | public void addNew(SNNew snNew) {
    method addNews (line 51) | public void addNews(ArrayList<SNNew> news) {
    method clear (line 57) | public void clear() {
    method size (line 61) | public int size(){
    method describeContents (line 66) | @Override
    method writeToParcel (line 71) | @Override
    method SNFeed (line 77) | public SNFeed() {
    method SNFeed (line 80) | protected SNFeed(Parcel in) {
    method createFromParcel (line 87) | @Override
    method newArray (line 92) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNNew.java
  class SNNew (line 21) | public class SNNew implements Parcelable {
    method SNNew (line 49) | public SNNew() {
    method SNNew (line 52) | public SNNew(String url, String title, String urlDomain, String voteUR...
    method SNNew (line 71) | public SNNew(String url, String title, String urlDomain, String voteUR...
    method getUrl (line 89) | public String getUrl() {
    method setUrl (line 93) | public void setUrl(String url) {
    method getTitle (line 97) | public String getTitle() {
    method setTitle (line 101) | public void setTitle(String title) {
    method getComHead (line 105) | public String getComHead() {
    method setComHead (line 112) | public void setComHead(String comHead) {
    method getSubText (line 116) | public String getSubText() {
    method setSubText (line 120) | public void setSubText(String subText) {
    method getUser (line 124) | public SNUser getUser() {
    method setUser (line 128) | public void setUser(SNUser user) {
    method getUrlDomain (line 132) | public String getUrlDomain() {
    method setUrlDomain (line 136) | public void setUrlDomain(String urlDomain) {
    method getVoteURL (line 142) | public String getVoteURL() {
    method setVoteURL (line 146) | public void setVoteURL(String voteURL) {
    method getCommentsCount (line 150) | public int getCommentsCount() {
    method setCommentsCount (line 154) | public void setCommentsCount(int commentsCount) {
    method getDiscussURL (line 158) | public String getDiscussURL() {
    method setDiscussURL (line 162) | public void setDiscussURL(String discussURL) {
    method getPostID (line 166) | public String getPostID() {
    method setPostID (line 170) | public void setPostID(String postID) {
    method getPoints (line 174) | public int getPoints() {
    method setPoints (line 178) | public void setPoints(int points) {
    method isDiscuss (line 182) | public boolean isDiscuss() {
    method setDiscuss (line 186) | public void setDiscuss(boolean isDiscuss) {
    method getText (line 190) | public String getText() {
    method setText (line 194) | public void setText(String text) {
    method getCreateat (line 198) | public String getCreateat() {
    method setCreateat (line 202) | public void setCreateat(String createat) {
    method toString (line 206) | @Override
    method describeContents (line 218) | @Override
    method writeToParcel (line 223) | @Override
    method SNNew (line 240) | protected SNNew(Parcel in) {
    method createFromParcel (line 257) | @Override
    method newArray (line 262) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNSession.java
  class SNSession (line 19) | public class SNSession implements Serializable {
    method SNSession (line 27) | public SNSession() {
    method SNSession (line 30) | public SNSession(String user, String id) {
    method getUser (line 36) | public String getUser() {
    method setUser (line 40) | public void setUser(String user) {
    method getId (line 44) | public String getId() {
    method setId (line 48) | public void setId(String id) {
    method clear (line 52) | public void clear() {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNUser.java
  class SNUser (line 19) | public class SNUser implements Serializable {
    method SNUser (line 36) | public SNUser() {
    method SNUser (line 40) | public SNUser(String id, String created, String karma, String about) {
    method getId (line 48) | public String getId() {
    method setId (line 52) | public void setId(String id) {
    method getCreated (line 56) | public String getCreated() {
    method setCreated (line 60) | public void setCreated(String created) {
    method getKarma (line 64) | public String getKarma() {
    method setKarma (line 68) | public void setKarma(String karma) {
    method getAbout (line 72) | public String getAbout() {
    method setAbout (line 76) | public void setAbout(String about) {
    method getName (line 80) | public String getName() {
    method setName (line 84) | public void setName(String name) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/Status.java
  class Status (line 6) | public class Status {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/exception/LoginException.java
  class LoginException (line 7) | public class LoginException extends Exception {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/exception/NetworkException.java
  class NetworkException (line 7) | public class NetworkException extends Exception {
    method NetworkException (line 14) | public NetworkException(int code, String message) {
    method NetworkException (line 19) | public NetworkException(int code, Throwable throwable) {
    method getCode (line 24) | public int getCode() {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/exception/SessionExpiredException.java
  class SessionExpiredException (line 7) | public class SessionExpiredException extends Exception {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/net/ISnApi.java
  type ISnApi (line 15) | public interface ISnApi {
    method getSNFeed (line 23) | Observable<SNFeed> getSNFeed(String url);
    method getFnid (line 25) | Observable<String> getFnid();
    method login (line 27) | Observable<String> login(String fnid, String username, String password);
    method getSNComments (line 29) | Observable<SNComments> getSNComments(String url);
    method upVote (line 37) | Observable<Status> upVote(String postId);
    method comment (line 46) | Observable<Status> comment(String text, String fnid);
    method logout (line 53) | Observable<Boolean> logout();
    method getDiscuss (line 60) | Observable<SNDiscuss> getDiscuss(String url);

FILE: data/src/main/java/com/halzhang/android/startupnews/data/net/JsoupConnector.java
  class JsoupConnector (line 29) | @Singleton
    method JsoupConnector (line 38) | public JsoupConnector(Context context, SessionManager sessionManager) {
    method newJsoupConnection (line 43) | public Connection newJsoupConnection(String url) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/net/SnApiImpl.java
  class SnApiImpl (line 47) | @Singleton
    method SnApiImpl (line 55) | public SnApiImpl(OkHttpClient okHttpClient, Context context, SessionMa...
    method getSNFeed (line 62) | @Override
    method getFnid (line 81) | @Override
    method login (line 115) | @Override
    method getSNComments (line 161) | @Override
    method upVote (line 180) | @Override
    method comment (line 220) | @Override
    method logout (line 261) | @Override
    method getDiscuss (line 293) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/BaseHTMLParser.java
  class BaseHTMLParser (line 32) | public abstract class BaseHTMLParser<T> {
    method parse (line 38) | public T parse(String input) throws Exception {
    method parseDocument (line 42) | public abstract T parseDocument(Document doc) throws Exception;
    method getDomainName (line 44) | public static String getDomainName(String url) {
    method getSafe (line 55) | public static <T extends Object> T getSafe(List<T> list, int index) {
    method getFirstTextValueInElementChildren (line 63) | public static String getFirstTextValueInElementChildren(Element elemen...
    method getStringValue (line 75) | public static String getStringValue(String query, Node source, XPath x...
    method getIntValueFollowedBySuffix (line 84) | public static Integer getIntValueFollowedBySuffix(String value, String...
    method getStringValuePrefixedByPrefix (line 100) | public static String getStringValuePrefixedByPrefix(String value, Stri...
    method resolveRelativeSNURL (line 108) | public static String resolveRelativeSNURL(String url) {
    method getCreateAt (line 124) | public String getCreateAt(String text) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParser.java
  class SNCommentsParser (line 24) | public class SNCommentsParser extends BaseHTMLParser<SNComments> {
    method SNCommentsParser (line 27) | public SNCommentsParser(){}
    method parseDocument (line 29) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParserV1.java
  class SNCommentsParserV1 (line 26) | public class SNCommentsParserV1 extends BaseHTMLParser<SNComments> {
    method parseDocument (line 28) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNDiscussParser.java
  class SNDiscussParser (line 25) | public class SNDiscussParser extends BaseHTMLParser<SNDiscuss> {
    method parseDocument (line 29) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNFeedParser.java
  class SNFeedParser (line 28) | public class SNFeedParser extends BaseHTMLParser<SNFeed> {
    method parseDocument (line 32) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/CookieFactoryImpl.java
  class CookieFactoryImpl (line 8) | public class CookieFactoryImpl implements CookieFactory {
    method CookieFactoryImpl (line 12) | public CookieFactoryImpl(SessionManager sessionManager) {
    method getCookie (line 16) | @Override

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/NetworkUtils.java
  class NetworkUtils (line 11) | public class NetworkUtils {
    method isNetworkAvailable (line 13) | public static boolean isNetworkAvailable(Context context) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/OkHttpClientHelper.java
  class OkHttpClientHelper (line 27) | @Singleton
    type CookieFactory (line 30) | public interface CookieFactory {
      method getCookie (line 31) | public String getCookie();
    method OkHttpClientHelper (line 50) | @Inject
    method init (line 57) | private void init(final Context context, CookieFactory factory) {
    method getOkHttpClient (line 122) | public OkHttpClient getOkHttpClient() {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/PersistentCookieStore.java
  class PersistentCookieStore (line 49) | public class PersistentCookieStore implements CookieStore {
    method PersistentCookieStore (line 62) | public PersistentCookieStore(Context context) {
    method add (line 84) | @Override
    method get (line 104) | @Override
    method getCookies (line 113) | @Override
    method getURIs (line 122) | @Override
    method remove (line 135) | @Override
    method removeAll (line 152) | @Override
    method getCookieToken (line 158) | protected String getCookieToken(URI uri, HttpCookie cookie) {
    method encodeCookie (line 167) | protected String encodeCookie(SerializableCookie cookie) {
    method decodeCookie (line 179) | protected HttpCookie decodeCookie(String cookieStr) {
    method byteArrayToHexString (line 195) | protected String byteArrayToHexString(byte[] b) {
    method hexStringToByteArray (line 207) | protected byte[] hexStringToByteArray(String s) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/PrefUtils.java
  class PrefUtils (line 9) | public class PrefUtils {
    method set (line 11) | public static void set(Context ctx, final String key, final String val...
    method get (line 15) | public static String get(Context context, final String key) {

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/SerializableCookie.java
  class SerializableCookie (line 32) | public class SerializableCookie implements Serializable {
    method SerializableCookie (line 38) | public SerializableCookie(HttpCookie cookie) {
    method getCookie (line 42) | public HttpCookie getCookie() {
    method writeObject (line 50) | private void writeObject(ObjectOutputStream out) throws IOException {
    method readObject (line 61) | private void readObject(ObjectInputStream in) throws IOException, Clas...

FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/SessionManager.java
  class SessionManager (line 26) | @Singleton
    method SessionManager (line 33) | public SessionManager(Context context) {
    method initSession (line 38) | public void initSession(Context context) {
    method storeSession (line 43) | public void storeSession(SNSession session) {
    method storeSession (line 48) | public void storeSession(String user, String id) {
    method saveSessionToPref (line 58) | private void saveSessionToPref() {
    method initSessionFromPref (line 67) | private void initSessionFromPref() {
    method clear (line 82) | public void clear() {
    method getSessionId (line 87) | public String getSessionId() {
    method getSessionUser (line 94) | public String getSessionUser() {
    method isValid (line 106) | public boolean isValid() {
    method getCookieString (line 110) | public String getCookieString() {
Condensed preview — 184 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (357K chars).
[
  {
    "path": ".gitignore",
    "chars": 391,
    "preview": "# built application files\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\ngen/\n\n"
  },
  {
    "path": ".travis.yml",
    "chars": 800,
    "preview": "language: android\njdk:\n  - openjdk7\n  - oraclejdk7\n  - oraclejdk8\n  \nenv:\n  matrix:\n    - ANDROID_TARGET=android-23  AND"
  },
  {
    "path": "3rdlib/commonlog/AndroidManifest.xml",
    "chars": 226,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.halzhang.android.common.log\"\n    a"
  },
  {
    "path": "3rdlib/commonlog/README.md",
    "chars": 41,
    "preview": "CommonLog\n=========\n\nAndroid Log library\n"
  },
  {
    "path": "3rdlib/commonlog/build.gradle",
    "chars": 947,
    "preview": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n}\napply plugin: 'com.android.library'\n\ndependencies {\n    "
  },
  {
    "path": "3rdlib/commonlog/build.xml",
    "chars": 3925,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"pulltorefresh\" default=\"help\">\n\n    <!-- The local.properties file"
  },
  {
    "path": "3rdlib/commonlog/proguard-project.txt",
    "chars": 781,
    "preview": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in t"
  },
  {
    "path": "3rdlib/commonlog/project.properties",
    "chars": 584,
    "preview": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# T"
  },
  {
    "path": "3rdlib/commonlog/src/com/halzhang/android/common/CDLog.java",
    "chars": 2486,
    "preview": "\npackage com.halzhang.android.common;\n\nimport android.util.Log;\n\n/**\n * CommonLog<br/>\n * log\n * \n * @author <a href=\"ht"
  },
  {
    "path": "3rdlib/commontoast/AndroidManifest.xml",
    "chars": 225,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.halzhang.android.common.toast\"\n   "
  },
  {
    "path": "3rdlib/commontoast/README.md",
    "chars": 47,
    "preview": "CommonToast\n===========\n\nAndroid Toast library\n"
  },
  {
    "path": "3rdlib/commontoast/build.gradle",
    "chars": 947,
    "preview": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n}\napply plugin: 'com.android.library'\n\ndependencies {\n    "
  },
  {
    "path": "3rdlib/commontoast/build.xml",
    "chars": 3925,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"pulltorefresh\" default=\"help\">\n\n    <!-- The local.properties file"
  },
  {
    "path": "3rdlib/commontoast/proguard-project.txt",
    "chars": 781,
    "preview": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in t"
  },
  {
    "path": "3rdlib/commontoast/project.properties",
    "chars": 584,
    "preview": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# T"
  },
  {
    "path": "3rdlib/commontoast/src/com/halzhang/android/common/CDToast.java",
    "chars": 1097,
    "preview": "\npackage com.halzhang.android.common;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.widget."
  },
  {
    "path": "README.md",
    "chars": 1461,
    "preview": "# [Startup News](http://news.dbanotes.net) Android App\n\n[![Build Status](https://travis-ci.org/halzhang/StartupNews.svg?"
  },
  {
    "path": "android-wait-for-emulator",
    "chars": 645,
    "preview": "#!/bin/bash\n\n# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain\n\nset +e\n\nboot"
  },
  {
    "path": "app/build.gradle",
    "chars": 4224,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'com.neenbedankt.android-apt'\n\nandroid {\n    def globalConfigurati"
  },
  {
    "path": "app/proguard-project.txt",
    "chars": 850,
    "preview": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in t"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 3307,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ApplicationModule.java",
    "chars": 482,
    "preview": "package com.halzhang.android.apps.startupnews;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/Constants.java",
    "chars": 1335,
    "preview": "\npackage com.halzhang.android.apps.startupnews;\n\n/**\n * Constants used by StartupNews application\n * \n * @author <a href"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/MyApplication.java",
    "chars": 6099,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews;\n\nimport android.app.Application;\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/SnApiComponent.java",
    "chars": 956,
    "preview": "package com.halzhang.android.apps.startupnews;\n\nimport com.halzhang.android.startupnews.data.CookieFactoryModule;\nimport"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/analytics/MyExceptionParser.java",
    "chars": 2360,
    "preview": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.apps."
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/analytics/Tracker.java",
    "chars": 1405,
    "preview": "package com.halzhang.android.apps.startupnews.analytics;\n\nimport android.content.Context;\nimport android.text.TextUtils;"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BasePresenter.java",
    "chars": 168,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\n/**\n * Created by Hal on 16/6/11.\n */\npublic interface BasePre"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BaseView.java",
    "chars": 192,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\n/**\n * Created by Hal on 16/6/11.\n */\npublic interface BaseVie"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListContract.java",
    "chars": 533,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.SNComment;"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenter.java",
    "chars": 3634,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\nimport android.text"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenterModule.java",
    "chars": 505,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module f"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussComponent.java",
    "chars": 510,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\ni"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussContract.java",
    "chars": 667,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenter.java",
    "chars": 3208,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\n\nimport com.halzhan"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenterModule.java",
    "chars": 430,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created "
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginComponent.java",
    "chars": 597,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\ni"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginContract.java",
    "chars": 505,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport rx.Subscription;\n\n/**\n * Contract for login presenter a"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenter.java",
    "chars": 2294,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\n\nimport com.halzhan"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenterModule.java",
    "chars": 457,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module f"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityContract.java",
    "chars": 682,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.Status;\n\n/"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenter.java",
    "chars": 2444,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\n\nimport com.halzhan"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenterModule.java",
    "chars": 460,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created "
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainComponent.java",
    "chars": 554,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\ni"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListContract.java",
    "chars": 527,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.SNNew;\n\nim"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListFragmentComponent.java",
    "chars": 532,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\ni"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenter.java",
    "chars": 3485,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\nimport android.text"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenterModule.java",
    "chars": 478,
    "preview": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module f"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/AboutActivity.java",
    "chars": 4410,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport android.os.Bundle;\nimp"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseActivity.java",
    "chars": 195,
    "preview": "package com.halzhang.android.apps.startupnews.ui;\n\nimport android.support.v7.app.AppCompatActivity;\n\n/**\n * Created by H"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseFragmentActivity.java",
    "chars": 1411,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport com.google.analytics.tr"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/DiscussActivity.java",
    "chars": 4243,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport com.halzhang.android.a"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/LauncherActivity.java",
    "chars": 151,
    "preview": "package com.halzhang.android.apps.startupnews.ui;\n\n/**\n * Created by Hal on 15/2/7.\n */\npublic class LauncherActivity ex"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/LoginActivity.java",
    "chars": 2551,
    "preview": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.apps."
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/MainActivity.java",
    "chars": 13533,
    "preview": "\npackage com.halzhang.android.apps.startupnews.ui;\n\n\nimport android.content.Intent;\nimport android.content.res.Configura"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/ShareHelper.java",
    "chars": 305,
    "preview": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\npackage com.halzhang.android.apps.s"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/SubmitActivity.java",
    "chars": 315,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport android.support.v4.app."
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/BaseFragment.java",
    "chars": 682,
    "preview": "package com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation."
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/CommentsListFragment.java",
    "chars": 6505,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.app.A"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/LoginFragment.java",
    "chars": 6960,
    "preview": "package com.halzhang.android.apps.startupnews.ui.fragment;\n\n\nimport android.animation.Animator;\nimport android.animation"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/NewsListFragment.java",
    "chars": 11738,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.app.A"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/SwipeRefreshRecyclerFragment.java",
    "chars": 4316,
    "preview": "package com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimpor"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/phone/BrowseActivity.java",
    "chars": 2162,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui.phone;\n\nimport com.halzhang.and"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/BrowseFragment.java",
    "chars": 6705,
    "preview": "\npackage com.halzhang.android.apps.startupnews.ui.tablet;\n\nimport android.annotation.TargetApi;\nimport android.app.Activ"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/DiscussFragment.java",
    "chars": 12284,
    "preview": "package com.halzhang.android.apps.startupnews.ui.tablet;\n\nimport android.app.Activity;\nimport android.content.Intent;\nim"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/CardViewDividerDecoration.java",
    "chars": 2043,
    "preview": "package com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content.Context;\nimport android.content.res.Ty"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/DividerDecoration.java",
    "chars": 2070,
    "preview": "package com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content.Context;\nimport android.content.res.Ty"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/ObservableWebView.java",
    "chars": 2464,
    "preview": "/*\n *\n * Copyright (C) 2013 HalZhang.\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.apps."
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/SwitchPreference.java",
    "chars": 496,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/WebViewController.java",
    "chars": 7816,
    "preview": "package com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.ha"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityScoped.java",
    "chars": 337,
    "preview": "package com.halzhang.android.apps.startupnews.utils;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotatio"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityUtils.java",
    "chars": 3390,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport android.app.Activit"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/AppUtils.java",
    "chars": 1212,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport com.halzhang.androi"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/CrashHandler.java",
    "chars": 4965,
    "preview": "\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport com.google.analytics.tracking.android.EasyTracker;\nimport "
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsActivityHelper.java",
    "chars": 3493,
    "preview": "package com.halzhang.android.apps.startupnews.utils;\n\nimport android.app.Activity;\nimport android.content.ComponentName;"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsHelper.java",
    "chars": 5354,
    "preview": "package com.halzhang.android.apps.startupnews.utils;\n\nimport android.content.Context;\nimport android.content.Intent;\nimp"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/DateUtils.java",
    "chars": 589,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport android.content.Cont"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/FragmentScoped.java",
    "chars": 337,
    "preview": "package com.halzhang.android.apps.startupnews.utils;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotatio"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/PreferenceUtils.java",
    "chars": 1391,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport com.halzhang.androi"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/UIUtils.java",
    "chars": 1924,
    "preview": "package com.halzhang.android.apps.startupnews.utils;\n\nimport android.annotation.TargetApi;\nimport android.content.Contex"
  },
  {
    "path": "app/src/main/res/anim/push_down_out.xml",
    "chars": 1002,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n     Licensed under the "
  },
  {
    "path": "app/src/main/res/anim/push_up_in.xml",
    "chars": 1002,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n     Licensed under the "
  },
  {
    "path": "app/src/main/res/anim/slide_in_left.xml",
    "chars": 1054,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_in_left.xml\n**\n** Copyright 2007, The"
  },
  {
    "path": "app/src/main/res/anim/slide_in_right.xml",
    "chars": 1054,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_in_right.xml\n**\n** Copyright 2007, Th"
  },
  {
    "path": "app/src/main/res/anim/slide_out_left.xml",
    "chars": 1055,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_out_left.xml\n**\n** Copyright 2007, Th"
  },
  {
    "path": "app/src/main/res/anim/slide_out_right.xml",
    "chars": 1055,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_out_right.xml\n**\n** Copyright 2007, T"
  },
  {
    "path": "app/src/main/res/drawable/action_bar_background.xml",
    "chars": 868,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2010 The Android Open Source Project\n\n     Licensed under the "
  },
  {
    "path": "app/src/main/res/drawable/background_boardless.xml",
    "chars": 258,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item "
  },
  {
    "path": "app/src/main/res/drawable/background_holo_dark.xml",
    "chars": 880,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2012 The Android Open Source Project\n\n     Licensed under the "
  },
  {
    "path": "app/src/main/res/drawable/background_holo_light.xml",
    "chars": 880,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2012 The Android Open Source Project\n\n     Licensed under the "
  },
  {
    "path": "app/src/main/res/drawable/bg_discuss_article.xml",
    "chars": 426,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/fragment_shadow.xml",
    "chars": 996,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ fragment_shadow.xml\n  ~ \n  ~ Copyright (C) 2013 6 Wunderkinder GmbH\n  ~\n"
  },
  {
    "path": "app/src/main/res/drawable/send_button_selector.xml",
    "chars": 915,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2011 Google Inc.\n\n     Licensed under the Apache License,"
  },
  {
    "path": "app/src/main/res/drawable/sidebar_shadow.xml",
    "chars": 982,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ sidebar_shadow.xml\n  ~ \n  ~ Copyright (C) 2013 6 Wunderkinder GmbH\n  ~\n "
  },
  {
    "path": "app/src/main/res/layout/actionbar_indeterminate_progress.xml",
    "chars": 410,
    "preview": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_height=\"wrap_content\"\n    and"
  },
  {
    "path": "app/src/main/res/layout/activity_browse.xml",
    "chars": 234,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "app/src/main/res/layout/activity_discuss.xml",
    "chars": 234,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "app/src/main/res/layout/activity_login.xml",
    "chars": 235,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 1691,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout android:id=\"@+id/main_content\"\n "
  },
  {
    "path": "app/src/main/res/layout/browse_bar.xml",
    "chars": 2049,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:lay"
  },
  {
    "path": "app/src/main/res/layout/comment_list_item.xml",
    "chars": 2565,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.CardView\n    xmlns:android=\"http://schemas.android.com"
  },
  {
    "path": "app/src/main/res/layout/discuss_comment_item.xml",
    "chars": 2065,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    an"
  },
  {
    "path": "app/src/main/res/layout/discuss_header_view.xml",
    "chars": 1360,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/fragment_browse.xml",
    "chars": 515,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    an"
  },
  {
    "path": "app/src/main/res/layout/fragment_discuss.xml",
    "chars": 2144,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "app/src/main/res/layout/fragment_login.xml",
    "chars": 2645,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "app/src/main/res/layout/news_list_item.xml",
    "chars": 2957,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.CardView\n    xmlns:android=\"http://schemas.android.com"
  },
  {
    "path": "app/src/main/res/layout/preference_toolbar.xml",
    "chars": 616,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/"
  },
  {
    "path": "app/src/main/res/layout/ptr_list_layout.xml",
    "chars": 1182,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "app/src/main/res/layout/refresh_recycler_view_layout.xml",
    "chars": 491,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v4.widget.SwipeRefreshLayout xmlns:android=\"http://schemas.andro"
  },
  {
    "path": "app/src/main/res/menu/activity_browse.xml",
    "chars": 475,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n   "
  },
  {
    "path": "app/src/main/res/menu/activity_login.xml",
    "chars": 284,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http"
  },
  {
    "path": "app/src/main/res/menu/activity_main.xml",
    "chars": 523,
    "preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_feedback\"\n  "
  },
  {
    "path": "app/src/main/res/menu/fragment_browse.xml",
    "chars": 519,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"h"
  },
  {
    "path": "app/src/main/res/menu/fragment_discuss.xml",
    "chars": 296,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    \n    <item"
  },
  {
    "path": "app/src/main/res/menu/fragment_news.xml",
    "chars": 423,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n   "
  },
  {
    "path": "app/src/main/res/values/analytics.xml",
    "chars": 543,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"Typog"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 1949,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     File created by the Android Action Bar Style Generator\n\n     Copyright "
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "chars": 1987,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- sliding layer -->\n    <dimen name=\"shadow_width\">8dp</dimen"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "chars": 645,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"pull_refresh_list\" type=\"id\"/>\n    <item name=\"menu_l"
  },
  {
    "path": "app/src/main/res/values/integers.xml",
    "chars": 721,
    "preview": "<!--\n  Copyright 2012 Google Inc.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use t"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 6334,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <!--<string "
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "chars": 2767,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Generated with http://android-holo-colors.com -->\n<resources xmlns:tools=\"htt"
  },
  {
    "path": "app/src/main/res/xml/preferences.xml",
    "chars": 2545,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2008 The Android Open Source Project\n\n     Licensed under"
  },
  {
    "path": "build.gradle",
    "chars": 547,
    "preview": "\napply from: 'buildsystem/dependencies.gradle'\n\n// Top-level build file where you can add configuration options common t"
  },
  {
    "path": "buildsystem/dependencies.gradle",
    "chars": 2245,
    "preview": "allprojects {\r\n    repositories {\r\n        jcenter()\r\n    }\r\n}\r\n\r\next {\r\n    //Android\r\n    androidBuildToolsVersion = \""
  },
  {
    "path": "changelog.md",
    "chars": 759,
    "preview": "Startup News Changelog\n----\n\n# v0.6.0.0(2013.5.13)\n* 增加登陆功能\n* 增加投票功能\n* 增加评论功能\n* 增加打开原网页功能\n* 更新内置浏览器布局\n\n#v0.5.2.0(2013.4."
  },
  {
    "path": "data/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "data/build.gradle",
    "chars": 1264,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'com.neenbedankt.android-apt'\n\nandroid {\n    def globalConfiguration ="
  },
  {
    "path": "data/proguard-rules.pro",
    "chars": 663,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "data/src/androidTest/java/com/halzhang/android/startupnews/data/ApplicationTest.java",
    "chars": 368,
    "preview": "package com.halzhang.android.startupnews.data;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;"
  },
  {
    "path": "data/src/main/AndroidManifest.xml",
    "chars": 300,
    "preview": "<manifest package=\"com.halzhang.android.startupnews.data\"\n          xmlns:android=\"http://schemas.android.com/apk/res/an"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/Constant.java",
    "chars": 546,
    "preview": "package com.halzhang.android.startupnews.data;\n\n/**\n * 常量\n * Created by Hal on 2016/5/30.\n */\npublic class Constant {\n\n "
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/CookieFactoryModule.java",
    "chars": 602,
    "preview": "package com.halzhang.android.startupnews.data;\n\nimport com.halzhang.android.startupnews.data.utils.CookieFactoryImpl;\nim"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/JsoupConnectorModule.java",
    "chars": 565,
    "preview": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/OkHttpClientModule.java",
    "chars": 647,
    "preview": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/SessionManagerModule.java",
    "chars": 494,
    "preview": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/SnApiModule.java",
    "chars": 782,
    "preview": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNComment.java",
    "chars": 2577,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializab"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNComments.java",
    "chars": 1314,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializab"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNDiscuss.java",
    "chars": 1744,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializab"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNFeed.java",
    "chars": 2020,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport android.os.Parcel;"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNNew.java",
    "chars": 6477,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport android.os.Parcel;"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNSession.java",
    "chars": 939,
    "preview": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.start"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNUser.java",
    "chars": 1504,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializab"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/Status.java",
    "chars": 484,
    "preview": "package com.halzhang.android.startupnews.data.entity;\n\n/**\n * Created by Hal on 16/6/3.\n */\npublic class Status {\n\n    /"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/exception/LoginException.java",
    "chars": 154,
    "preview": "package com.halzhang.android.startupnews.data.exception;\n\n/**\n * 登录异常\n * Created by Hal on 15/12/3.\n */\npublic class Log"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/exception/NetworkException.java",
    "chars": 506,
    "preview": "package com.halzhang.android.startupnews.data.exception;\n\n/**\n * 网络异常\n * Created by Hal on 16/6/5.\n */\npublic class Netw"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/exception/SessionExpiredException.java",
    "chars": 169,
    "preview": "package com.halzhang.android.startupnews.data.exception;\n\n/**\n * session 过期\n * Created by Hal on 16/8/24.\n */\npublic cla"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/net/ISnApi.java",
    "chars": 1240,
    "preview": "package com.halzhang.android.startupnews.data.net;\n\nimport com.halzhang.android.startupnews.data.entity.SNComments;\nimpo"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/net/JsoupConnector.java",
    "chars": 1317,
    "preview": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.start"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/net/SnApiImpl.java",
    "chars": 13407,
    "preview": "package com.halzhang.android.startupnews.data.net;\n\nimport android.content.Context;\nimport android.content.Intent;\nimpor"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/BaseHTMLParser.java",
    "chars": 3645,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\nimport android.text.TextU"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParser.java",
    "chars": 3761,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\nimport com.halzhang.andro"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParserV1.java",
    "chars": 2501,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\n\nimport com.halzhang.andr"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNDiscussParser.java",
    "chars": 5474,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\n\nimport com.halzhang.andr"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNFeedParser.java",
    "chars": 4678,
    "preview": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\nimport android.util.Log;\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/CookieFactoryImpl.java",
    "chars": 497,
    "preview": "package com.halzhang.android.startupnews.data.utils;\n\nimport com.halzhang.android.startupnews.data.utils.OkHttpClientHel"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/NetworkUtils.java",
    "chars": 921,
    "preview": "package com.halzhang.android.startupnews.data.utils;\r\n\r\nimport android.content.Context;\r\nimport android.net.Connectivity"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/OkHttpClientHelper.java",
    "chars": 4818,
    "preview": "package com.halzhang.android.startupnews.data.utils;\n\nimport android.content.Context;\nimport android.os.Build;\nimport an"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/PersistentCookieStore.java",
    "chars": 7641,
    "preview": "/*\n    Android Asynchronous Http Client\n    Copyright (c) 2011 James Smith <james@loopj.com>\n    http://loopj.com\n\n    L"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/PrefUtils.java",
    "chars": 548,
    "preview": "package com.halzhang.android.startupnews.data.utils;\n\nimport android.content.Context;\nimport android.preference.Preferen"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/SerializableCookie.java",
    "chars": 2528,
    "preview": "/*\n    Android Asynchronous Http Client\n    Copyright (c) 2011 James Smith <james@loopj.com>\n    http://loopj.com\n\n    L"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/SessionManager.java",
    "chars": 2791,
    "preview": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.start"
  },
  {
    "path": "data/src/main/res/values/strings.xml",
    "chars": 127,
    "preview": "<resources>\n    <string name=\"app_name\">data</string>\n    <string name=\"pref_key_cookie\">pref_key_cookie</string>\n</reso"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 231,
    "preview": "#Thu Nov 26 14:56:12 CST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradlew",
    "chars": 5080,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "settings.gradle",
    "chars": 91,
    "preview": "include ':data',\n        ':3rdlib:commonlog',\n        ':3rdlib:commontoast',\n        ':app'"
  },
  {
    "path": "wait_for_emulator.sh",
    "chars": 473,
    "preview": "#!/bin/bash\n\n# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain\n\nset +e\n\nboot"
  }
]

// ... and 14 more files (download for full content)

About this extraction

This page contains the full source code of the halzhang/StartupNews GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 184 files (318.2 KB), approximately 78.2k tokens, and a symbol index with 654 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!