[
  {
    "path": ".gitignore",
    "content": "# 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# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Eclipse project files\n.classpath\n.project\n.settings\n\n#build\nant.properties\n*.keystore\nStartupNews/app/src/main/res/values/analytics.xml\n\n# IntelliJ files\n.idea\n*.iml\nout/\nmodule_*.xml\n\n#gradle\nbuild/\n.gradle/"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\njdk:\n  - openjdk7\n  - oraclejdk7\n  - oraclejdk8\n  \nenv:\n  matrix:\n    - ANDROID_TARGET=android-23  ANDROID_ABI=armeabi-v7a\n\nandroid:\n  components:\n    - tools\n    - platform-tools\n    - build-tools-23.0.1\n    - android-23\n    - extra-android-support\n    - extra-google-google_play_services\n    - extra-google-m2repository\n    - extra-android-m2repository\n\nnotifications:\n  email: true\n\n# Turn off caching to avoid any caching problems\ncache: false\n# Use the Travis Container-Based Infrastructure (see #203)\nsudo: false\n\nbefore_script:\n  - echo no | android create avd --force -n test -t android-19 --abi armeabi-v7a\n  - emulator -avd test -no-skin -no-audio -no-window &\n  - android-wait-for-emulator\n  - adb shell input keyevent 82 &\n\nscript: ./gradlew assembleDebug connectedCheck"
  },
  {
    "path": "3rdlib/commonlog/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.halzhang.android.common.log\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\" >\n\n    <application >\n    </application>\n\n</manifest>"
  },
  {
    "path": "3rdlib/commonlog/README.md",
    "content": "CommonLog\n=========\n\nAndroid Log library\n"
  },
  {
    "path": "3rdlib/commonlog/build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n}\napply plugin: 'com.android.library'\n\ndependencies {\n    compile fileTree(dir:'libs',include:'*.jar')\n}\n\nandroid {\n    def globalConfiguration = rootProject.extensions.getByName(\"ext\")\n\n    compileSdkVersion globalConfiguration.getAt(\"androidCompileSdkVersion\")\n    buildToolsVersion globalConfiguration.getAt(\"androidBuildToolsVersion\")\n\n    defaultConfig {\n        minSdkVersion globalConfiguration.getAt(\"androidMinSdkVersion\")\n        targetSdkVersion globalConfiguration.getAt(\"androidTargetSdkVersion\")\n    }\n    sourceSets {\n        main {\n            manifest.srcFile 'AndroidManifest.xml'\n            java.srcDirs = ['src']\n            resources.srcDirs = ['src']\n            aidl.srcDirs = ['src']\n            renderscript.srcDirs = ['src']\n            res.srcDirs = ['res']\n            assets.srcDirs = ['assets']\n        }\n\n        instrumentTest.setRoot('tests')\n    }\n}\n"
  },
  {
    "path": "3rdlib/commonlog/build.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"pulltorefresh\" default=\"help\">\n\n    <!-- The local.properties file is created and updated by the 'android' tool.\n         It contains the path to the SDK. It should *NOT* be checked into\n         Version Control Systems. -->\n    <property file=\"local.properties\" />\n\n    <!-- The ant.properties file can be created by you. It is only edited by the\n         'android' tool to add properties to it.\n         This is the place to change some Ant specific build properties.\n         Here are some properties you may want to change/update:\n\n         source.dir\n             The name of the source directory. Default is 'src'.\n         out.dir\n             The name of the output directory. Default is 'bin'.\n\n         For other overridable properties, look at the beginning of the rules\n         files in the SDK, at tools/ant/build.xml\n\n         Properties related to the SDK location or the project target should\n         be updated using the 'android' tool with the 'update' action.\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems.\n\n         -->\n    <property file=\"ant.properties\" />\n\n    <!-- if sdk.dir was not set from one of the property file, then\n         get it from the ANDROID_HOME env var.\n         This must be done before we load project.properties since\n         the proguard config can use sdk.dir -->\n    <property environment=\"env\" />\n    <condition property=\"sdk.dir\" value=\"${env.ANDROID_HOME}\">\n        <isset property=\"env.ANDROID_HOME\" />\n    </condition>\n\n    <!-- The project.properties file is created and updated by the 'android'\n         tool, as well as ADT.\n\n         This contains project specific properties such as project target, and library\n         dependencies. Lower level build properties are stored in ant.properties\n         (or in .classpath for Eclipse projects).\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems. -->\n    <loadproperties srcFile=\"project.properties\" />\n\n    <!-- quick check on sdk.dir -->\n    <fail\n            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.\"\n            unless=\"sdk.dir\"\n    />\n\n    <!--\n        Import per project custom build rules if present at the root of the project.\n        This is the place to put custom intermediary targets such as:\n            -pre-build\n            -pre-compile\n            -post-compile (This is typically used for code obfuscation.\n                           Compiled code location: ${out.classes.absolute.dir}\n                           If this is not done in place, override ${out.dex.input.absolute.dir})\n            -post-package\n            -post-build\n            -pre-clean\n    -->\n    <import file=\"custom_rules.xml\" optional=\"true\" />\n\n    <!-- Import the actual build file.\n\n         To customize existing targets, there are two options:\n         - Customize only one target:\n             - copy/paste the target into this file, *before* the\n               <import> task.\n             - customize it to your needs.\n         - Customize the whole content of build.xml\n             - copy/paste the content of the rules files (minus the top node)\n               into this file, replacing the <import> task.\n             - customize to your needs.\n\n         ***********************\n         ****** IMPORTANT ******\n         ***********************\n         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,\n         in order to avoid having your file be overridden by tools such as \"android update project\"\n    -->\n    <!-- version-tag: 1 -->\n    <import file=\"${sdk.dir}/tools/ant/build.xml\" />\n\n</project>\n"
  },
  {
    "path": "3rdlib/commonlog/proguard-project.txt",
    "content": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in that file.\n#\n# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the ProGuard\n# include property in project.properties.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "3rdlib/commonlog/project.properties",
    "content": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# This file must be checked in Version Control Systems.\n#\n# To customize properties used by the Ant build system edit\n# \"ant.properties\", and override values to adapt the script to your\n# project structure.\n#\n# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):\n#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt\n\n# Project target.\ntarget=android-17\nandroid.library=true\n"
  },
  {
    "path": "3rdlib/commonlog/src/com/halzhang/android/common/CDLog.java",
    "content": "\npackage com.halzhang.android.common;\n\nimport android.util.Log;\n\n/**\n * CommonLog<br/>\n * log\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version ec 24, 2012 9:55:18 PM\n */\npublic class CDLog {\n    public static final String LOG_TAG = CDLog.class.getSimpleName();\n\n    public static final boolean DEBUG = true;\n\n    private static final String LOG_PREFIX = \"CDLOG_\";\n\n    private static final int LOG_PREFIX_LENGTH = LOG_PREFIX.length();\n\n    private static final int MAX_LOG_TAG_LENGTH = 23;\n\n    public static String makeLogTag(String str) {\n        if (str.length() > MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH) {\n            return LOG_PREFIX + str.substring(0, MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH - 1);\n        }\n        return LOG_PREFIX + str;\n    }\n\n    /**\n     * WARNING: Don't use this when obfuscating class names with Proguard!\n     */\n    public static String makeLogTag(Class<?> cls) {\n        return makeLogTag(cls.getSimpleName());\n    }\n\n    public static void i(String tag, String msg, Throwable throwable) {\n        if (DEBUG) {\n            Log.i(tag, msg, throwable);\n        }\n    }\n\n    public static void i(String tag, Object... objects) {\n        StringBuilder builder = new StringBuilder();\n        for (Object obj : objects) {\n            builder.append(obj);\n        }\n        i(tag, builder.toString());\n    }\n\n    public static void i(String tag, String msg) {\n        i(tag, msg, null);\n    }\n\n    public static void i(String msg) {\n        i(LOG_TAG, msg, null);\n    }\n\n    public static void d(String tag, String msg, Throwable throwable) {\n        if (DEBUG) {\n            Log.d(tag, msg, throwable);\n        }\n    }\n\n    public static void d(String tag, String msg) {\n        d(tag, msg, null);\n    }\n\n    public static void d(String msg) {\n        d(LOG_TAG, msg, null);\n    }\n\n    public static void w(String tag, String msg, Throwable throwable) {\n        if (DEBUG) {\n            Log.w(tag, msg, throwable);\n        }\n    }\n\n    public static void w(String tag, String msg) {\n        w(tag, msg, null);\n    }\n\n    public static void w(String msg) {\n        w(LOG_TAG, msg, null);\n    }\n\n    public static void e(String tag, String msg, Throwable throwable) {\n        if (DEBUG) {\n            Log.e(tag, msg, throwable);\n        }\n    }\n\n    public static void e(String tag, String msg) {\n        e(tag, msg, null);\n    }\n\n    public static void e(String msg) {\n        e(LOG_TAG, msg, null);\n    }\n\n    private CDLog() {\n    }\n}\n"
  },
  {
    "path": "3rdlib/commontoast/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.halzhang.android.common.toast\"\n    android:versionCode=\"1\"\n    android:versionName=\"1.0\" >\n\n    <applicatio>\n    </applicatio>\n\n</manifest>"
  },
  {
    "path": "3rdlib/commontoast/README.md",
    "content": "CommonToast\n===========\n\nAndroid Toast library\n"
  },
  {
    "path": "3rdlib/commontoast/build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n}\napply plugin: 'com.android.library'\n\ndependencies {\n    compile fileTree(dir:'libs',include:'*.jar')\n}\n\nandroid {\n    def globalConfiguration = rootProject.extensions.getByName(\"ext\")\n\n    compileSdkVersion globalConfiguration.getAt(\"androidCompileSdkVersion\")\n    buildToolsVersion globalConfiguration.getAt(\"androidBuildToolsVersion\")\n\n    defaultConfig {\n        minSdkVersion globalConfiguration.getAt(\"androidMinSdkVersion\")\n        targetSdkVersion globalConfiguration.getAt(\"androidTargetSdkVersion\")\n    }\n    sourceSets {\n        main {\n            manifest.srcFile 'AndroidManifest.xml'\n            java.srcDirs = ['src']\n            resources.srcDirs = ['src']\n            aidl.srcDirs = ['src']\n            renderscript.srcDirs = ['src']\n            res.srcDirs = ['res']\n            assets.srcDirs = ['assets']\n        }\n\n        instrumentTest.setRoot('tests')\n    }\n}\n"
  },
  {
    "path": "3rdlib/commontoast/build.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project name=\"pulltorefresh\" default=\"help\">\n\n    <!-- The local.properties file is created and updated by the 'android' tool.\n         It contains the path to the SDK. It should *NOT* be checked into\n         Version Control Systems. -->\n    <property file=\"local.properties\" />\n\n    <!-- The ant.properties file can be created by you. It is only edited by the\n         'android' tool to add properties to it.\n         This is the place to change some Ant specific build properties.\n         Here are some properties you may want to change/update:\n\n         source.dir\n             The name of the source directory. Default is 'src'.\n         out.dir\n             The name of the output directory. Default is 'bin'.\n\n         For other overridable properties, look at the beginning of the rules\n         files in the SDK, at tools/ant/build.xml\n\n         Properties related to the SDK location or the project target should\n         be updated using the 'android' tool with the 'update' action.\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems.\n\n         -->\n    <property file=\"ant.properties\" />\n\n    <!-- if sdk.dir was not set from one of the property file, then\n         get it from the ANDROID_HOME env var.\n         This must be done before we load project.properties since\n         the proguard config can use sdk.dir -->\n    <property environment=\"env\" />\n    <condition property=\"sdk.dir\" value=\"${env.ANDROID_HOME}\">\n        <isset property=\"env.ANDROID_HOME\" />\n    </condition>\n\n    <!-- The project.properties file is created and updated by the 'android'\n         tool, as well as ADT.\n\n         This contains project specific properties such as project target, and library\n         dependencies. Lower level build properties are stored in ant.properties\n         (or in .classpath for Eclipse projects).\n\n         This file is an integral part of the build system for your\n         application and should be checked into Version Control Systems. -->\n    <loadproperties srcFile=\"project.properties\" />\n\n    <!-- quick check on sdk.dir -->\n    <fail\n            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.\"\n            unless=\"sdk.dir\"\n    />\n\n    <!--\n        Import per project custom build rules if present at the root of the project.\n        This is the place to put custom intermediary targets such as:\n            -pre-build\n            -pre-compile\n            -post-compile (This is typically used for code obfuscation.\n                           Compiled code location: ${out.classes.absolute.dir}\n                           If this is not done in place, override ${out.dex.input.absolute.dir})\n            -post-package\n            -post-build\n            -pre-clean\n    -->\n    <import file=\"custom_rules.xml\" optional=\"true\" />\n\n    <!-- Import the actual build file.\n\n         To customize existing targets, there are two options:\n         - Customize only one target:\n             - copy/paste the target into this file, *before* the\n               <import> task.\n             - customize it to your needs.\n         - Customize the whole content of build.xml\n             - copy/paste the content of the rules files (minus the top node)\n               into this file, replacing the <import> task.\n             - customize to your needs.\n\n         ***********************\n         ****** IMPORTANT ******\n         ***********************\n         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,\n         in order to avoid having your file be overridden by tools such as \"android update project\"\n    -->\n    <!-- version-tag: 1 -->\n    <import file=\"${sdk.dir}/tools/ant/build.xml\" />\n\n</project>\n"
  },
  {
    "path": "3rdlib/commontoast/proguard-project.txt",
    "content": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in that file.\n#\n# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the ProGuard\n# include property in project.properties.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "3rdlib/commontoast/project.properties",
    "content": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# This file must be checked in Version Control Systems.\n#\n# To customize properties used by the Ant build system edit\n# \"ant.properties\", and override values to adapt the script to your\n# project structure.\n#\n# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):\n#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt\n\n# Project target.\ntarget=android-17\nandroid.library=true\n"
  },
  {
    "path": "3rdlib/commontoast/src/com/halzhang/android/common/CDToast.java",
    "content": "\npackage com.halzhang.android.common;\n\nimport android.content.Context;\nimport android.os.Handler;\nimport android.widget.Toast;\n\n/**\n * CommonToast<br/>\n * Toast\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Dec 27, 2012 11:39:39 AM\n */\npublic class CDToast {\n\n    private static Toast sToast;\n\n    private static Handler mHandler;\n\n    /**\n     * @param context\n     * @param text\n     */\n    public static void showToast(Context context, String text) {\n        if (sToast == null) {\n            mHandler = new Handler(context.getMainLooper());\n            sToast = Toast.makeText(context.getApplicationContext(), text, Toast.LENGTH_SHORT);\n        } else {\n            sToast.setText(text);\n        }\n        mHandler.post(new Runnable() {\n\n            @Override\n            public void run() {\n                sToast.show();\n            }\n        });\n    }\n\n    /**\n     * @param context\n     * @param resId\n     */\n    public static void showToast(Context context, int resId) {\n        String text = context.getString(resId);\n        showToast(context, text);\n    }\n\n}\n"
  },
  {
    "path": "README.md",
    "content": "# [Startup News](http://news.dbanotes.net) Android App\n\n[![Build Status](https://travis-ci.org/halzhang/StartupNews.svg?branch=master)](https://travis-ci.org/halzhang/StartupNews)\n\n[![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) \n![image](https://raw.github.com/halzhang/StartupNews/master/QR.jpg)\n[本地下载](https://github.com/halzhang/StartupNews/raw/master/release/StartupNews-release.apk)\n支持系统\n---\nAndroid 4.0+\n\n反馈\n---\n邮箱：[ghanguo@gmail.com](mailto:ghanguo@gmail.com)\n\n欢迎直接在[issues](https://github.com/halzhang/StartupNews/issues/new)提bug；\n\n作者\n---\n[HalZhang](http://weibo.com/halzhang)\n\n感谢  [@Fenng](http://www.weibo.com/fenng) 创建了Startup News.\n\nChangelog\n---\n[changelog](https://github.com/halzhang/StartupNews/wiki/Changelog)\n\nLicense\n--------\n\n    Copyright 2016 Zhang Hanguo\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n"
  },
  {
    "path": "android-wait-for-emulator",
    "content": "#!/bin/bash\n\n# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain\n\nset +e\n\nbootanim=\"\"\nfailcounter=0\ntimeout_in_sec=360\n\nuntil [[ \"$bootanim\" =~ \"stopped\" ]]; do\n  bootanim=`adb -e shell getprop init.svc.bootanim 2>&1 &`\n  if [[ \"$bootanim\" =~ \"device not found\" || \"$bootanim\" =~ \"device offline\"\n    || \"$bootanim\" =~ \"running\" ]]; then\n    let \"failcounter += 1\"\n    echo \"Waiting for emulator to start\"\n    if [[ $failcounter -gt timeout_in_sec ]]; then\n      echo \"Timeout ($timeout_in_sec seconds) reached; failed to start emulator\"\n      exit 1\n    fi\n  fi\n  sleep 1\ndone\n\necho \"Emulator is ready\""
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'com.neenbedankt.android-apt'\n\nandroid {\n    def globalConfiguration = rootProject.extensions.getByName(\"ext\")\n\n    def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()\n\n    compileSdkVersion globalConfiguration.getAt(\"androidCompileSdkVersion\")\n    buildToolsVersion globalConfiguration.getAt(\"androidBuildToolsVersion\")\n\n    defaultConfig {\n        minSdkVersion globalConfiguration.getAt(\"androidMinSdkVersion\")\n        targetSdkVersion globalConfiguration.getAt(\"androidTargetSdkVersion\")\n        applicationId \"com.halzhang.android.apps.startupnews\"\n        versionCode 13\n        versionName \"2.0.0-${gitSha}\"\n    }\n\n    signingConfigs {\n        release\n    }\n\n    File storeFile = file(\"../local.properties\")\n    if (storeFile.canRead()) {\n        Properties p = new Properties()\n        p.load(storeFile.newDataInputStream())\n        if (p != null && p.containsKey('key.store') && p.containsKey(\"key.store.password\")\n                && p.containsKey(\"key.alias\") && p.containsKey(\"key.alias.password\")) {\n            android.signingConfigs.release.storeFile = file(p.getProperty(\"key.store\"))\n            android.signingConfigs.release.storePassword = p.getProperty(\"key.store.password\")\n            android.signingConfigs.release.keyAlias = p.getProperty(\"key.alias\")\n            android.signingConfigs.release.keyPassword = p.getProperty(\"key.alias.password\")\n        } else {\n            android.buildTypes.release.signingConfig = null\n        }\n    } else {\n        android.buildTypes.release.signingConfig = null\n    }\n\n    buildTypes {\n        debug {\n            debuggable true\n            versionNameSuffix \"-debug\"\n            applicationIdSuffix \".debug\"\n            resValue \"string\", \"app_name\", \"SN(debug)\"\n        }\n\n        release {\n\n            applicationVariants.all { variant ->\n                variant.outputs.each { output ->\n                    if (output.outputFile != null && output.outputFile.name.endsWith('.apk')\n                            && 'release'.equals(variant.buildType.name)) {\n                        def apkFile = new File(\n                                output.outputFile.getParent(),\n                                \"StartupNews_${variant.flavorName}_v${variant.versionName}.apk\")\n                        output.outputFile = apkFile\n                    }\n                }\n            }\n\n\n            resValue \"string\", \"app_name\", \"StartupNews\"\n            shrinkResources true\n            debuggable false\n            signingConfig signingConfigs.release\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'\n        }\n\n    }\n\n    productFlavors {\n        googleplay {}\n    }\n\n}\n\ndependencies {\n\n    def androidSupportVersion = rootProject.ext.androidSupportVersion\n    def mvpVersion = rootProject.ext.mvpVersion\n\n    compile fileTree(dir: 'libs', include: '*.jar')\n    compile \"com.android.support:appcompat-v7:${androidSupportVersion}\"\n    compile \"com.android.support:support-v4:${androidSupportVersion}\"\n    compile \"com.android.support:support-annotations:${androidSupportVersion}\"\n    compile \"com.android.support:recyclerview-v7:${androidSupportVersion}\"\n    compile \"com.android.support:cardview-v7:${androidSupportVersion}\"\n    compile \"com.android.support:design:${androidSupportVersion}\"\n    compile \"com.android.support:customtabs:${androidSupportVersion}\"\n    compile \"com.github.halzhang:mvp:${mvpVersion}\"\n    compile \"com.github.halzhang:mvp-support-v4:${mvpVersion}\"\n    compile \"com.github.halzhang:mvp-support-v7:${mvpVersion}\"\n    // Dagger dependencies\n    apt \"com.google.dagger:dagger-compiler:$rootProject.daggerVersion\"\n    provided 'org.glassfish:javax.annotation:10.0-b28'\n    compile \"com.google.dagger:dagger:$rootProject.daggerVersion\"\n\n    compile project(':3rdlib:commonlog')\n    compile project(':3rdlib:commontoast')\n    compile project(':data')\n    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'\n    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'\n    compile 'org.jsoup:jsoup:1.7.2'\n    compile 'com.android.support:support-v4:23.4.0'\n}\n"
  },
  {
    "path": "app/proguard-project.txt",
    "content": "# To enable ProGuard in your project, edit project.properties\n# to define the proguard.config property as described in that file.\n#\n# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in ${sdk.dir}/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the ProGuard\n# include property in project.properties.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n-keep class org.jsoup.** { *; }\n-keep interface org.jsoup.** { *; }\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.halzhang.android.apps.startupnews\">\n\n    <supports-screens\n        android:anyDensity=\"true\"\n        android:xlargeScreens=\"true\" />\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <!-- Gmail permission -->\n    <uses-permission android:name=\"com.google.android.gm.permission.AUTO_SEND\" />\n\n    <application\n        android:name=\".MyApplication\"\n        android:allowBackup=\"true\"\n        android:description=\"@string/app_description\"\n        android:icon=\"@drawable/ic_launcher\">\n\n        <activity\n            android:name=\".ui.MainActivity\"\n            android:label=\"@string/app_name\"\n            android:theme=\"@style/AppTheme\"\n            android:configChanges=\"uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".ui.phone.BrowseActivity\"\n            android:theme=\"@style/AppPreferenceTheme\"\n            android:configChanges=\"uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize\" />\n        <activity\n            android:name=\".ui.DiscussActivity\"\n            android:theme=\"@style/AppTheme\"\n            android:configChanges=\"uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize\"\n            android:label=\"@string/title_discuss\" />\n        <!--\n             <activity\n            android:name=\".ui.SubmitActivity\"\n            android:configChanges=\"uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale\"\n            android:label=\"@string/title_shareto_startupnews\"\n            android:screenOrientation=\"portrait\">\n            <intent-filter >\n                <action android:name=\"android.intent.action.SEND\"/>\n                <data android:mimeType=\"text/plain\"/>\n                <category android:name=\"android.intent.category.DEFAULT\"/>\n            </intent-filter>\n\n        </activity>\n        -->\n        <activity\n            android:name=\".ui.AboutActivity\"\n            android:theme=\"@style/AppPreferenceTheme\"\n            android:configChanges=\"uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize\"\n            android:label=\"@string/title_settings\" />\n        <activity\n            android:name=\".ui.LoginActivity\"\n            android:theme=\"@style/AppPreferenceTheme\"\n            android:configChanges=\"uiMode|mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|fontScale|screenSize\"\n            android:label=\"@string/title_login\"\n            android:windowSoftInputMode=\"stateHidden\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ApplicationModule.java",
    "content": "package com.halzhang.android.apps.startupnews;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews.data.utils.OkHttpClientHelper;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 2016/5/30.\n */\n@Module\npublic class ApplicationModule {\n\n    private final Context mContext;\n\n\n    public ApplicationModule(Context context) {\n        mContext = context;\n    }\n\n    @Provides\n    Context provideContext() {\n        return mContext;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/Constants.java",
    "content": "\npackage com.halzhang.android.apps.startupnews;\n\n/**\n * Constants used by StartupNews application\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Jan 16, 2013 11:46:03 AM\n */\npublic abstract class Constants {\n\n    /**\n     * abs class\n     */\n    protected Constants() {\n\n    }\n\n    /**\n     * Root Dir used by SN app\n     */\n    public static final String SDCARD_TOP_DIR = \"/SN/\";\n\n    /**\n     * Crash log dir\n     */\n    public static final String CRASH_LOG_DIR = SDCARD_TOP_DIR+\"/crash/\";\n\n    /**\n     * 历史记录保存文件名\n     */\n    public static final String HISTORY_FILE_NAME = \"history.db\";\n\n    public final class IntentAction {\n\n        /**\n         * 登陆后结果cookie user\n         * \n         * @see #ACTION_LOGIN\n         */\n        public static final String EXTRA_LOGIN_USER = \"startupnews.intent.extra.login.USER\";\n\n        /**\n         * 登陆结果\n         * <p>\n         * 输出：登陆后的cookie extras:LOGIN_USER(value:String)\n         * </p>\n         * \n         * @see #EXTRA_LOGIN_USER\n         */\n        public static final String ACTION_LOGIN = \"startupnews.intent.action.LOGIN\";\n        \n        /**\n         * 注销\n         */\n        public static final String ACTION_LOGOUT = \"startupnews.intent.action.LOGOUT\";\n        \n    }\n    \n    public static final String TAG_BROWSE_FRAGMENT = \"tag_browse_fragment\";\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/MyApplication.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews;\n\nimport android.app.Application;\nimport android.os.StrictMode;\nimport android.text.TextUtils;\n\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.utils.CrashHandler;\nimport com.halzhang.android.common.CDLog;\nimport com.halzhang.android.startupnews.data.CookieFactoryModule;\nimport com.halzhang.android.startupnews.data.OkHttpClientModule;\nimport com.halzhang.android.startupnews.data.SnApiModule;\nimport com.squareup.leakcanary.LeakCanary;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * StartupNews\n * <p>\n * app\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 8, 2013\n */\npublic class MyApplication extends Application {\n\n    private static final String LOG_TAG = MyApplication.class.getSimpleName();\n\n    private HashSet<String> mHistorySet = new HashSet<String>();\n\n    private static final ThreadFactory sThreadFactory = new ThreadFactory() {\n        private final AtomicInteger mCount = new AtomicInteger(1);\n\n        public Thread newThread(Runnable r) {\n            return new Thread(r, \"StartupNews thread #\" + mCount.getAndIncrement());\n        }\n    };\n\n    private ExecutorService mExecutorService;\n\n    private static MyApplication me;\n\n    public static MyApplication instance() {\n        return me;\n    }\n\n    public MyApplication() {\n        super();\n        me = this;\n    }\n\n    private SnApiComponent mSnApiComponent;\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        Tracker.getInstance().init(this);\n        CrashHandler.getInstance().init(this);\n        mExecutorService = Executors.newSingleThreadExecutor(sThreadFactory);\n        initHistory();\n        if (BuildConfig.DEBUG) {\n            StrictMode.setThreadPolicy(new StrictMode\n                    .ThreadPolicy.Builder()\n                    .detectDiskReads()\n                    .detectDiskWrites()\n                    .detectNetwork()\n                    .penaltyLog()\n                    .build());\n            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()\n                    .detectActivityLeaks()\n                    .detectLeakedClosableObjects()\n                    .penaltyLog()\n                    .build());\n            LeakCanary.install(this);\n        }\n\n        mSnApiComponent = DaggerSnApiComponent.builder().applicationModule(new ApplicationModule(this))\n                .okHttpClientModule(new OkHttpClientModule())\n                .snApiModule(new SnApiModule())\n                .cookieFactoryModule(new CookieFactoryModule()).build();\n        mSnApiComponent.getSessionManager().initSession(this);\n    }\n\n    public SnApiComponent getSnApiComponent() {\n        return mSnApiComponent;\n    }\n\n    private void initHistory() {\n        File file = new File(getFilesDir().getAbsolutePath() + File.separator\n                + Constants.HISTORY_FILE_NAME);\n        if (!file.exists()) {\n            return;\n        }\n        BufferedReader reader = null;\n        try {\n            reader = new BufferedReader(new FileReader(file));\n            String s = null;\n            while (!TextUtils.isEmpty((s = reader.readLine()))) {\n                mHistorySet.add(s);\n            }\n        } catch (FileNotFoundException e) {\n            CDLog.e(LOG_TAG, \"\", e);\n            Tracker.getInstance().sendException(\"History file not found!\", e, false);\n        } catch (IOException e) {\n            CDLog.e(LOG_TAG, \"\", e);\n            Tracker.getInstance().sendException(\"Read History file error!\", e, false);\n        } finally {\n            if (reader != null) {\n                try {\n                    reader.close();\n                } catch (IOException e) {\n                }\n            }\n        }\n    }\n\n    private void storeHistory() {\n        if (!mHistorySet.isEmpty()) {\n            Iterator<String> iterator = mHistorySet.iterator();\n            StringBuilder builder = new StringBuilder();\n            while (iterator.hasNext()) {\n                builder.append(iterator.next()).append(\"\\n\");\n            }\n            File file = new File(getFilesDir().getAbsolutePath() + File.separator\n                    + Constants.HISTORY_FILE_NAME);\n            if (!file.exists()) {\n                try {\n                    file.createNewFile();\n                } catch (IOException e) {\n                    CDLog.e(LOG_TAG, \"Create history file error!\");\n                    Tracker.getInstance().sendException(\"Create history file error!\", e, false);\n                }\n            }\n            PrintWriter writer = null;\n            try {\n                writer = new PrintWriter(file);\n                writer.write(builder.toString());\n                writer.close();\n            } catch (FileNotFoundException e) {\n                CDLog.e(LOG_TAG, \"History file not found!\");\n                Tracker.getInstance().sendException(\"History file not found!\", e, false);\n            } finally {\n                if (writer != null) {\n                    writer.close();\n                }\n            }\n        }\n    }\n\n    /**\n     * 增加访问历史记录\n     *\n     * @param url\n     */\n    public void addHistory(String url) {\n        if (!TextUtils.isEmpty(url) && !mHistorySet.contains(url)) {\n            mHistorySet.add(url);\n            mExecutorService.submit(new Runnable() {\n\n                @Override\n                public void run() {\n                    storeHistory();\n                }\n            });\n        }\n    }\n\n    /**\n     * 清空历史记录\n     */\n    public void clearHistory() {\n        mHistorySet.clear();\n    }\n\n    public boolean isHistoryContains(String url) {\n        return mHistorySet.contains(url);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/SnApiComponent.java",
    "content": "package com.halzhang.android.apps.startupnews;\n\nimport com.halzhang.android.startupnews.data.CookieFactoryModule;\nimport com.halzhang.android.startupnews.data.JsoupConnectorModule;\nimport com.halzhang.android.startupnews.data.OkHttpClientModule;\nimport com.halzhang.android.startupnews.data.SessionManagerModule;\nimport com.halzhang.android.startupnews.data.net.ISnApi;\nimport com.halzhang.android.startupnews.data.SnApiModule;\nimport com.halzhang.android.startupnews.data.net.JsoupConnector;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Singleton;\n\nimport dagger.Component;\n\n/**\n * Created by Hal on 2016/5/30.\n */\n@Singleton\n@Component(modules = {ApplicationModule.class, SnApiModule.class, OkHttpClientModule.class,\n        SessionManagerModule.class, JsoupConnectorModule.class, CookieFactoryModule.class})\npublic interface SnApiComponent {\n\n    ISnApi getSnApi();\n\n    SessionManager getSessionManager();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/analytics/MyExceptionParser.java",
    "content": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.apps.startupnews.analytics;\n\nimport com.google.analytics.tracking.android.ExceptionParser;\nimport com.halzhang.android.apps.startupnews.utils.AppUtils;\n\nimport android.content.Context;\nimport android.os.Build;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.lang.reflect.Field;\nimport java.net.UnknownHostException;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * StartupNews\n * <p>\n * 异常解析\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 26, 2013\n */\npublic class MyExceptionParser implements ExceptionParser {\n\n    private Context mContext;\n\n    private Map<String, String> infos = new HashMap<String, String>();\n\n    public MyExceptionParser(Context context) {\n        mContext = context;\n    }\n\n    @Override\n    public String getDescription(String message, Throwable throwable) {\n        collectDeviceInfo(mContext);\n        StringBuilder sb = new StringBuilder();\n        for (Map.Entry<String, String> entry : infos.entrySet()) {\n            String key = entry.getKey();\n            String value = entry.getValue();\n            sb.append(key).append(\"=\").append(value).append(\"\\n\");\n        }\n        sb.append(getStackTraceString(throwable));\n        return sb.toString();\n    }\n\n    public static String getStackTraceString(Throwable tr) {\n        if (tr == null) {\n            return \"\";\n        }\n        Throwable t = tr;\n        while (t != null) {\n            if (t instanceof UnknownHostException) {\n                return \"\";\n            }\n            t = t.getCause();\n        }\n        StringWriter sw = new StringWriter();\n        PrintWriter pw = new PrintWriter(sw);\n        tr.printStackTrace(pw);\n        return sw.toString();\n    }\n\n    public void collectDeviceInfo(Context ctx) {\n        infos.clear();\n        infos.put(\"versionName\", AppUtils.getVersionName(mContext));\n        infos.put(\"versionCode\", String.valueOf(AppUtils.getVersionCode(mContext)));\n        Field[] fields = Build.class.getDeclaredFields();\n        for (Field field : fields) {\n            try {\n                field.setAccessible(true);\n                infos.put(field.getName(), field.get(null).toString());\n            } catch (Exception e) {\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/analytics/Tracker.java",
    "content": "package com.halzhang.android.apps.startupnews.analytics;\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.google.analytics.tracking.android.EasyTracker;\nimport com.google.analytics.tracking.android.MapBuilder;\n\n/**\n * 统计\n * Created by Hal on 15/5/6.\n */\npublic class Tracker {\n\n    private static final String LOG_TAG = \"Tracker\";\n\n    private EasyTracker mEasyTracker;\n\n    private Tracker() {\n\n    }\n\n    private static class InstanceHolder {\n        private static final Tracker INSTANCE = new Tracker();\n    }\n\n    public static Tracker getInstance() {\n        return InstanceHolder.INSTANCE;\n    }\n\n    public void init(Context context) {\n        mEasyTracker = EasyTracker.getInstance(context);\n    }\n\n    public void sendException(String message, Throwable e, boolean fatal) {\n        if(mEasyTracker == null){\n            Log.w(LOG_TAG,\"Tracker has not init!\");\n            return;\n        }\n        mEasyTracker.send(MapBuilder.createException(TextUtils.isEmpty(message) ? (e == null ? \"\" : e.getMessage()) : message, fatal).build());\n    }\n\n    public void sendEvent(String category, String action, String label, Long value) {\n        if(mEasyTracker == null){\n            Log.w(LOG_TAG,\"Tracker has not init!\");\n            return;\n        }\n        mEasyTracker.send(MapBuilder.createEvent(category, action, label, value).build());\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BasePresenter.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\n/**\n * Created by Hal on 16/6/11.\n */\npublic interface BasePresenter {\n\n    void start();\n    void stop();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/BaseView.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\n/**\n * Created by Hal on 16/6/11.\n */\npublic interface BaseView<T> {\n\n    void setPresenter(T presenter);\n\n    boolean isActive();\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListContract.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.SNComment;\n\nimport java.util.ArrayList;\n\n/**\n * Created by Hal on 16/8/14.\n */\npublic interface CommentsListContract {\n\n    interface Presenter extends BasePresenter {\n        void getComments(String url);\n\n        void getMoreComments();\n    }\n\n    interface View extends BaseView<Presenter> {\n        void onSuccess(ArrayList<SNComment> snComments);\n\n        void onFailure(Throwable e);\n\n        void onAtEnd();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenter.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\nimport android.text.TextUtils;\n\nimport com.halzhang.android.startupnews.data.entity.SNComments;\nimport com.halzhang.android.startupnews.data.net.ISnApi;\n\nimport javax.inject.Inject;\n\nimport rx.Subscriber;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.schedulers.Schedulers;\n\n/**\n * Created by Hal on 16/8/14.\n */\npublic class CommentsListPresenter implements CommentsListContract.Presenter {\n\n    @NonNull\n    private final ISnApi mSnApi;\n    @NonNull\n    private final CommentsListContract.View mView;\n\n    private SNComments mComments;\n\n    @Inject\n    public CommentsListPresenter(@NonNull ISnApi snApi, @NonNull CommentsListContract.View view) {\n        mSnApi = snApi;\n        mView = view;\n    }\n\n    @Inject\n    void setupListener() {\n        mView.setPresenter(this);\n    }\n\n    @Override\n    public void getComments(String url) {\n        mSnApi.getSNComments(url)\n                .subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<SNComments>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        if (mView.isActive()) {\n                            mView.onFailure(e);\n                        }\n                    }\n\n                    @Override\n                    public void onNext(SNComments snComments) {\n                        if (snComments != null) {\n                            mComments = snComments;\n                            if (mView.isActive()) {\n                                mView.onSuccess(mComments.getSnComments());\n                            }\n                        }\n                    }\n                });\n    }\n\n    @Override\n    public void getMoreComments() {\n        if (mComments == null || TextUtils.isEmpty(mComments.getMoreURL())) {\n            if (mView.isActive()) {\n                mView.onAtEnd();\n            }\n        } else {\n            mSnApi.getSNComments(mComments.getMoreURL())\n                    .subscribeOn(Schedulers.io())\n                    .observeOn(AndroidSchedulers.mainThread())\n                    .subscribe(new Subscriber<SNComments>() {\n                        @Override\n                        public void onCompleted() {\n\n                        }\n\n                        @Override\n                        public void onError(Throwable e) {\n                            if (mView.isActive()) {\n                                mView.onFailure(e);\n                            }\n                        }\n\n                        @Override\n                        public void onNext(SNComments snComments) {\n                            if (snComments != null) {\n                                if (mComments == null) {\n                                    mComments = snComments;\n                                } else {\n                                    mComments.addComments(snComments.getSnComments());\n                                    mComments.setMoreURL(snComments.getMoreURL());\n                                }\n                                if (mView.isActive()) {\n                                    mView.onSuccess(mComments.getSnComments());\n                                }\n                            }\n                        }\n                    });\n        }\n    }\n\n    /* no-op */\n    @Override\n    public void start() {\n    }\n\n    @Override\n    public void stop() {\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/CommentsListPresenterModule.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module for Comments provide Comments view\n * Created by Hal on 16/6/12.\n */\n@Module\npublic class CommentsListPresenterModule {\n\n    private final CommentsListContract.View mView;\n\n    public CommentsListPresenterModule(CommentsListContract.View view) {\n        mView = view;\n    }\n\n    @Provides\n    CommentsListContract.View provideCommentsListContractView() {\n        return mView;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussComponent.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.ui.DiscussActivity;\nimport com.halzhang.android.apps.startupnews.utils.ActivityScoped;\n\nimport dagger.Component;\n\n/**\n * Created by Hal on 16/8/14.\n */\n@ActivityScoped\n@Component(dependencies = SnApiComponent.class, modules = DiscussPresenterModule.class)\npublic interface DiscussComponent {\n\n    void inject(DiscussActivity discussActivity);\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussContract.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;\nimport com.halzhang.android.startupnews.data.entity.Status;\n\n/**\n * Created by Hal on 16/8/14.\n */\npublic interface DiscussContract {\n\n    interface Presenter extends BasePresenter {\n        void getDiscuss(String url);\n\n        void comment(String message);\n    }\n\n    interface View extends BaseView<Presenter> {\n        void onGetDiscuss(SNDiscuss snDiscuss);\n\n        void onGetDiscussFailure(Throwable e);\n\n        void onCommentSuccess(Status status);\n\n        void onCommentFailure(Throwable e);\n\n        void onSessionExpired();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenter.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\n\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;\nimport com.halzhang.android.startupnews.data.entity.Status;\nimport com.halzhang.android.startupnews.data.net.ISnApi;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Inject;\n\nimport rx.Subscriber;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.schedulers.Schedulers;\n\n/**\n * Created by Hal on 16/8/14.\n */\npublic class DiscussPresenter implements DiscussContract.Presenter {\n\n    @NonNull\n    private final DiscussContract.View mView;\n\n    @NonNull\n    private final ISnApi mSnApi;\n\n    @NonNull\n    private final SessionManager mSessionManager;\n\n    private String mFnid;\n\n    @Inject\n    public DiscussPresenter(DiscussContract.View view, ISnApi snApi, SessionManager sessionManager) {\n        mView = view;\n        mSnApi = snApi;\n        mSessionManager = sessionManager;\n    }\n\n    @Inject\n    void setupListener() {\n        mView.setPresenter(this);\n    }\n\n    @Override\n    public void start() {\n\n    }\n\n    @Override\n    public void stop() {\n\n    }\n\n    @Override\n    public void getDiscuss(String url) {\n        mSnApi.getDiscuss(url)\n                .subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<SNDiscuss>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        if (mView.isActive()) {\n                            mView.onGetDiscussFailure(e);\n                        }\n                    }\n\n                    @Override\n                    public void onNext(SNDiscuss snDiscuss) {\n                        if (mView.isActive()) {\n                            if (snDiscuss != null) {\n                                mFnid = snDiscuss.getFnid();\n                            }\n                            mView.onGetDiscuss(snDiscuss);\n                        }\n                    }\n                });\n    }\n\n    @Override\n    public void comment(String message) {\n        if (!mSessionManager.isValid()) {\n            if (mView.isActive()) {\n                mView.onSessionExpired();\n            }\n            return;\n        }\n        mSnApi.comment(message, mFnid)\n                .subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<Status>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        if (mView.isActive()) {\n                            mView.onCommentFailure(e);\n                        }\n                    }\n\n                    @Override\n                    public void onNext(Status status) {\n                        if (mView.isActive()) {\n                            mView.onCommentSuccess(status);\n                        }\n                    }\n                });\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/DiscussPresenterModule.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 16/8/14.\n */\n@Module\npublic class DiscussPresenterModule {\n\n    private DiscussContract.View mView;\n\n    public DiscussPresenterModule(DiscussContract.View view) {\n        mView = view;\n    }\n\n    @Provides\n    public DiscussContract.View provideDiscussContractView() {\n        return mView;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginComponent.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.ui.LoginActivity;\nimport com.halzhang.android.apps.startupnews.utils.ActivityScoped;\nimport com.halzhang.android.apps.startupnews.utils.FragmentScoped;\n\nimport dagger.Component;\n\n/**\n * Dagger component for login\n * Created by Hal on 16/6/12.\n */\n@ActivityScoped\n@Component(dependencies = SnApiComponent.class, modules = LoginPresenterModule.class)\npublic interface LoginComponent {\n\n    void inject(LoginActivity loginActivity);\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginContract.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport rx.Subscription;\n\n/**\n * Contract for login presenter and view\n * Created by Hal on 16/6/12.\n */\npublic interface LoginContract {\n\n    interface Presenter extends BasePresenter {\n        void login(String username, String password);\n    }\n\n    interface View extends BaseView<Presenter> {\n\n        void onLoginError(Throwable e);\n\n        void onLoginResult(String user);\n\n        void addSubscription(Subscription subscription);\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenter.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\n\nimport com.halzhang.android.startupnews.data.net.ISnApi;\n\nimport javax.inject.Inject;\n\nimport rx.Observable;\nimport rx.Subscriber;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.functions.Func1;\nimport rx.schedulers.Schedulers;\n\n/**\n * Presenter for Login\n * Created by Hal on 16/6/12.\n */\npublic class LoginPresenter implements LoginContract.Presenter {\n\n    @NonNull\n    private final LoginContract.View mView;\n\n    @NonNull\n    private final ISnApi mSnApi;\n\n    @Inject\n    public LoginPresenter(@NonNull LoginContract.View view, @NonNull ISnApi snApi) {\n        mView = view;\n        mSnApi = snApi;\n    }\n\n    @Inject\n    void setupListener() {\n        mView.setPresenter(this);\n    }\n\n    @Override\n    public void login(final String username, final String password) {\n        mView.addSubscription(\n                mSnApi.getFnid()\n                        .flatMap(new Func1<String, Observable<String>>() {\n                            @Override\n                            public Observable<String> call(String s) {\n                                return mSnApi.login(s, username, password);\n                            }\n                        })\n                        .subscribeOn(Schedulers.io())\n                        .observeOn(AndroidSchedulers.mainThread())\n                        .subscribe(new Subscriber<String>() {\n                            @Override\n                            public void onCompleted() {\n\n                            }\n\n                            @Override\n                            public void onError(Throwable e) {\n                                if (mView.isActive()) {\n                                    mView.onLoginError(e);\n                                }\n                            }\n\n                            @Override\n                            public void onNext(String s) {\n                                if (mView.isActive()) {\n                                    mView.onLoginResult(s);\n                                }\n                            }\n                        })\n        );\n    }\n\n    /* no-op */\n    @Override\n    public void start() {\n\n    }\n\n    @Override\n    public void stop() {\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/LoginPresenterModule.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module for login provide login view\n * Created by Hal on 16/6/12.\n */\n@Module\npublic class LoginPresenterModule {\n\n    private final LoginContract.View mView;\n\n    public LoginPresenterModule(LoginContract.View view) {\n        mView = view;\n    }\n\n    @Provides\n    LoginContract.View provideLoginContractView() {\n        return mView;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityContract.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.Status;\n\n/**\n * Created by Hal on 16/8/23.\n */\npublic interface MainActivityContract {\n\n    interface Presenter extends BasePresenter {\n        void logout();\n\n        /**\n         * 投票，顶！d=====(￣▽￣*)b\n         *\n         * @param postId\n         */\n        void upVote(String postId);\n    }\n\n    interface View extends BaseView<Presenter> {\n\n        /**\n         * 注销登录\n         *\n         * @param result true 成功\n         */\n        void onLogoutResult(boolean result);\n\n        void onUpVoteFailure(Throwable e);\n\n        void onUpVoteSuccess(Status status);\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenter.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\n\nimport com.halzhang.android.startupnews.data.entity.Status;\nimport com.halzhang.android.startupnews.data.net.ISnApi;\n\nimport javax.inject.Inject;\n\nimport rx.Subscriber;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.schedulers.Schedulers;\n\n/**\n * Created by Hal on 16/8/23.\n */\npublic class MainActivityPresenter implements MainActivityContract.Presenter {\n\n    @NonNull\n    private final ISnApi mSnApi;\n    @NonNull\n    private final MainActivityContract.View mView;\n\n    @Inject\n    public MainActivityPresenter(ISnApi snApi, MainActivityContract.View view) {\n        mSnApi = snApi;\n        mView = view;\n    }\n\n    @Inject\n    void setupListener() {\n        mView.setPresenter(this);\n    }\n\n    @Override\n    public void logout() {\n        mSnApi.logout().subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<Boolean>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n\n                    }\n\n                    @Override\n                    public void onNext(Boolean aBoolean) {\n                        if (mView.isActive()) {\n                            mView.onLogoutResult(aBoolean);\n                        }\n                    }\n                });\n    }\n\n    @Override\n    public void upVote(String postId) {\n        mSnApi.upVote(postId)\n                .subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<Status>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        if (mView.isActive()) {\n                            mView.onUpVoteFailure(e);\n                        }\n                    }\n\n                    @Override\n                    public void onNext(Status status) {\n                        if (mView.isActive()) {\n                            mView.onUpVoteSuccess(status);\n                        }\n                    }\n                });\n    }\n\n    @Override\n    public void start() {\n\n    }\n\n    @Override\n    public void stop() {\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainActivityPresenterModule.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 16/8/23.\n */\n@Module\npublic class MainActivityPresenterModule {\n\n    private MainActivityContract.View mView;\n\n    public MainActivityPresenterModule(MainActivityContract.View view) {\n        mView = view;\n    }\n\n    @Provides\n    public MainActivityContract.View provideMainActivityContractView() {\n        return mView;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/MainComponent.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.ui.MainActivity;\nimport com.halzhang.android.apps.startupnews.utils.ActivityScoped;\n\nimport dagger.Component;\n\n/**\n * 主页\n * Created by Hal on 16/8/14.\n */\n@ActivityScoped\n@Component(dependencies = SnApiComponent.class, modules = {CommentsListPresenterModule.class,\n        MainActivityPresenterModule.class})\npublic interface MainComponent {\n\n    void inject(MainActivity mainActivity);\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListContract.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.startupnews.data.entity.SNNew;\n\nimport java.util.ArrayList;\n\n/**\n * Created by Hal on 16/8/14.\n */\npublic interface NewsListContract {\n\n    interface Presenter extends BasePresenter {\n        void getFeed(String url);\n\n        void getMoreFeed();\n    }\n\n    interface View extends BaseView<NewsListContract.Presenter> {\n        void onSuccess(ArrayList<SNNew> snNews);\n\n        void onFailure(Throwable e);\n\n        void onAtEnd();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListFragmentComponent.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.ui.fragment.NewsListFragment;\nimport com.halzhang.android.apps.startupnews.utils.FragmentScoped;\n\nimport dagger.Component;\n\n/**\n * Created by Hal on 16/8/14.\n */\n@FragmentScoped\n@Component(dependencies = SnApiComponent.class, modules = NewsListPresenterModule.class)\npublic interface NewsListFragmentComponent {\n\n    void inject(NewsListFragment newsListFragment);\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenter.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport android.support.annotation.NonNull;\nimport android.text.TextUtils;\n\nimport com.halzhang.android.startupnews.data.entity.SNFeed;\nimport com.halzhang.android.startupnews.data.net.ISnApi;\n\nimport javax.inject.Inject;\n\nimport rx.Subscriber;\nimport rx.android.schedulers.AndroidSchedulers;\nimport rx.schedulers.Schedulers;\n\n/**\n * 最新文章列表\n * Created by Hal on 2016/5/12.\n */\npublic class NewsListPresenter implements NewsListContract.Presenter {\n\n    @NonNull\n    private final ISnApi mSnApi;\n\n    @NonNull\n    private final NewsListContract.View mView;\n\n    private SNFeed mSNFeed;\n\n    @Inject\n    public NewsListPresenter(ISnApi snApi, NewsListContract.View view) {\n        mSnApi = snApi;\n        mView = view;\n    }\n\n    @Inject\n    void setupListener() {\n        mView.setPresenter(this);\n    }\n\n    @Override\n    public void start() {\n\n    }\n\n    @Override\n    public void stop() {\n\n    }\n\n    @Override\n    public void getFeed(String url) {\n        mSnApi.getSNFeed(url)\n                .subscribeOn(Schedulers.io())\n                .observeOn(AndroidSchedulers.mainThread())\n                .subscribe(new Subscriber<SNFeed>() {\n                    @Override\n                    public void onCompleted() {\n\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        if (mView.isActive()) {\n                            mView.onFailure(e);\n                        }\n                    }\n\n                    @Override\n                    public void onNext(SNFeed snFeed) {\n                        if (snFeed != null) {\n                            mSNFeed = snFeed;\n                            if (mView.isActive()) {\n                                mView.onSuccess(mSNFeed.getSnNews());\n                            }\n                        }\n                    }\n                });\n    }\n\n    @Override\n    public void getMoreFeed() {\n        if (mSNFeed != null && !TextUtils.isEmpty(mSNFeed.getMoreUrl())) {\n            mSnApi.getSNFeed(mSNFeed.getMoreUrl())\n                    .subscribeOn(Schedulers.io())\n                    .observeOn(AndroidSchedulers.mainThread())\n                    .subscribe(new Subscriber<SNFeed>() {\n                        @Override\n                        public void onCompleted() {\n\n                        }\n\n                        @Override\n                        public void onError(Throwable e) {\n                            if (mView.isActive()) {\n                                mView.onFailure(e);\n                            }\n                        }\n\n                        @Override\n                        public void onNext(SNFeed snFeed) {\n                            if (snFeed != null) {\n                                if (mSNFeed == null) {\n                                    mSNFeed = snFeed;\n                                } else {\n                                    mSNFeed.setMoreUrl(snFeed.getMoreUrl());\n                                    mSNFeed.addNews(snFeed.getSnNews());\n                                }\n                                if (mView.isActive()) {\n                                    mView.onSuccess(mSNFeed.getSnNews());\n                                }\n                            }\n                        }\n                    });\n        } else {\n            if (mView.isActive()) {\n                mView.onAtEnd();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/presenter/NewsListPresenterModule.java",
    "content": "package com.halzhang.android.apps.startupnews.presenter;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module for News List provide newt view\n * Created by Hal on 16/6/12.\n */\n@Module\npublic class NewsListPresenterModule {\n\n    private final NewsListContract.View mView;\n\n    public NewsListPresenterModule(NewsListContract.View view) {\n        mView = view;\n    }\n\n    @Provides\n    NewsListContract.View provideNewsListContractView() {\n        return mView;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/AboutActivity.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport android.os.Bundle;\nimport android.preference.ListPreference;\nimport android.preference.Preference;\nimport android.preference.Preference.OnPreferenceChangeListener;\nimport android.preference.PreferenceFragment;\nimport android.support.v7.app.ActionBar;\nimport android.view.MenuItem;\n\nimport com.google.analytics.tracking.android.EasyTracker;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.utils.AppUtils;\n\n/**\n * StartupNews\n * <p>\n * 设置\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 8, 2013\n */\npublic class AboutActivity extends BaseFragmentActivity {\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        final ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setDisplayHomeAsUpEnabled(true);\n        }\n        getFragmentManager().beginTransaction().replace(android.R.id.content, new AppPreferenceFragment()).commit();\n\n    }\n\n    @Override\n    protected void onPostCreate(Bundle savedInstanceState) {\n        super.onPostCreate(savedInstanceState);\n//        LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();\n//        Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.preference_toolbar, root, false);\n//        root.addView(bar, 0); // insert at top\n//        bar.setTitleTextColor(getResources().getColor(android.R.color.white));\n//        bar.setNavigationOnClickListener(new View.OnClickListener() {\n//            @Override\n//            public void onClick(View v) {\n//                finish();\n//            }\n//        });\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                break;\n\n            default:\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    public static class AppPreferenceFragment extends PreferenceFragment implements OnPreferenceChangeListener {\n        @Override\n        public void onCreate(Bundle savedInstanceState) {\n            super.onCreate(savedInstanceState);\n            addPreferencesFromResource(R.xml.preferences);\n            ListPreference listPreference = (ListPreference) findPreference(getString(R.string.pref_key_html_provider));\n            listPreference.setOnPreferenceChangeListener(this);\n            listPreference.setSummary(listPreference.getEntry());\n            (findPreference(getString(R.string.pref_key_default_browse)))\n                    .setOnPreferenceChangeListener(this);\n            Preference versionPref = findPreference(getString(R.string.pref_key_version));\n            versionPref.setSummary(getString(R.string.pref_summary_version,\n                    AppUtils.getVersionName(getActivity())));\n        }\n\n        @Override\n        public boolean onPreferenceChange(Preference preference, Object newValue) {\n            final String key = preference.getKey();\n            if (getString(R.string.pref_key_html_provider).equals(key)) {\n                Tracker.getInstance().sendEvent(\"preference_change_action\",\n                        \"preference_change_html_provider\",\n                        String.format(\"html_provider_%1$s\", (String) newValue), 0L);\n                ListPreference listPreference = (ListPreference) preference;\n                preference.setSummary(listPreference.getEntries()[listPreference\n                        .findIndexOfValue((String) newValue)]);\n            } else if (getString(R.string.pref_key_default_browse).equals(key)) {\n                Tracker.getInstance().sendEvent(\"preference_change_action\",\n                        \"preference_change_default_browse\",\n                        String.format(\"default_browse_%1$s\", String.valueOf(newValue)), 0L);\n            }\n            return true;\n        }\n    }\n\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        EasyTracker.getInstance(this).activityStart(this);\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        EasyTracker.getInstance(this).activityStop(this);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseActivity.java",
    "content": "package com.halzhang.android.apps.startupnews.ui;\n\nimport android.support.v7.app.AppCompatActivity;\n\n/**\n * Created by Hal on 16/6/18.\n */\npublic class BaseActivity extends AppCompatActivity {\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/BaseFragmentActivity.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport com.google.analytics.tracking.android.EasyTracker;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.mvp.presenter.Presenter;\nimport com.halzhang.android.mvp.support.v7.MVPAppCompatActivity;\n\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.Window;\nimport android.view.WindowManager;\n\n/**\n * StartupNews\n * <p>\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 13, 2013\n */\npublic class BaseFragmentActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(Bundle arg0) {\n        super.onCreate(arg0);\n//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){\n//            Window w = getWindow();\n//            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);\n//            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n//        }\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        EasyTracker.getInstance(this).activityStart(this);\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        EasyTracker.getInstance(this).activityStop(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/DiscussActivity.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport com.halzhang.android.apps.startupnews.Constants.IntentAction;\nimport com.halzhang.android.apps.startupnews.MyApplication;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.presenter.DaggerDiscussComponent;\nimport com.halzhang.android.apps.startupnews.presenter.DiscussPresenter;\nimport com.halzhang.android.apps.startupnews.presenter.DiscussPresenterModule;\nimport com.halzhang.android.apps.startupnews.ui.tablet.DiscussFragment;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.Bundle;\nimport android.support.v7.app.ActionBar;\nimport android.text.TextUtils;\nimport android.view.Menu;\nimport android.view.MenuItem;\n\nimport javax.inject.Inject;\n\n/**\n * StartupNews\n * <p>\n * 评论界面\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 17, 2013\n */\npublic class DiscussActivity extends BaseFragmentActivity {\n\n    //private static final String LOG_TAG = DiscussActivity.class.getSimpleName();\n\n    public static final String ARG_DISCUSS_URL = \"discuss_url\";//required!\n\n    public static final String ARG_SNNEW = \"snnew\";//Optional\n\n    public static void start(Context context, String discussUrl, SNNew snNew) {\n        Intent starter = new Intent(context, DiscussActivity.class);\n        starter.putExtra(ARG_DISCUSS_URL, discussUrl);\n        starter.putExtra(ARG_SNNEW, snNew);\n        context.startActivity(starter);\n    }\n\n    @Inject\n    DiscussPresenter mDiscussPresenter;\n\n    private DiscussFragment mDiscussFragment;\n\n    private BroadcastReceiver mReceiver = new BroadcastReceiver() {\n\n        @Override\n        public void onReceive(android.content.Context context, Intent intent) {\n            final String action = intent.getAction();\n            if (IntentAction.ACTION_LOGIN.equals(action)) {\n                String user = intent.getStringExtra(IntentAction.EXTRA_LOGIN_USER);\n                if (!TextUtils.isEmpty(user)) {\n                    mDiscussFragment.loadData();\n                }\n            }\n        }\n\n        ;\n    };\n\n    @Override\n    protected void onCreate(Bundle arg0) {\n        super.onCreate(arg0);\n        setContentView(R.layout.activity_discuss);\n        ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n        }\n        SNNew snNew = (SNNew) getIntent().getSerializableExtra(ARG_SNNEW);\n        String mDiscussURL = getIntent().getStringExtra(ARG_DISCUSS_URL);\n        if (TextUtils.isEmpty(mDiscussURL)) {\n            finish();\n        }\n        mDiscussFragment = DiscussFragment.newInstance(mDiscussURL,snNew);\n        getSupportFragmentManager().beginTransaction()\n                .add(R.id.fragment_container, mDiscussFragment).commitAllowingStateLoss();\n        IntentFilter filter = new IntentFilter(IntentAction.ACTION_LOGIN);\n        registerReceiver(mReceiver, filter);\n        SnApiComponent snApiComponent = ((MyApplication) getApplication()).getSnApiComponent();\n        DaggerDiscussComponent.builder().snApiComponent(snApiComponent)\n                .discussPresenterModule(new DiscussPresenterModule(mDiscussFragment)).build().inject(this);\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        invalidateOptionsMenu();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        unregisterReceiver(mReceiver);\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        super.onCreateOptionsMenu(menu);\n        return true;\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/LauncherActivity.java",
    "content": "package com.halzhang.android.apps.startupnews.ui;\n\n/**\n * Created by Hal on 15/2/7.\n */\npublic class LauncherActivity extends BaseFragmentActivity {\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/LoginActivity.java",
    "content": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport android.os.Bundle;\nimport android.support.v7.app.ActionBar;\nimport android.view.MenuItem;\n\nimport com.google.analytics.tracking.android.EasyTracker;\nimport com.google.analytics.tracking.android.MapBuilder;\nimport com.halzhang.android.apps.startupnews.MyApplication;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.presenter.DaggerLoginComponent;\nimport com.halzhang.android.apps.startupnews.presenter.LoginPresenter;\nimport com.halzhang.android.apps.startupnews.presenter.LoginPresenterModule;\nimport com.halzhang.android.apps.startupnews.ui.fragment.LoginFragment;\n\nimport javax.inject.Inject;\n\n/**\n * StartupNews\n * <p>\n * 登陆\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Apr 20, 2013\n */\npublic class LoginActivity extends BaseActivity {\n\n    private static final String LOG_TAG = LoginActivity.class.getSimpleName();\n\n    @Inject\n    LoginPresenter mPresenter;\n\n    private LoginFragment mLoginFragment;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        final ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setDisplayHomeAsUpEnabled(true);\n        }\n        setContentView(R.layout.activity_login);\n        mLoginFragment = LoginFragment.newInstance();\n        SnApiComponent snApiComponent = ((MyApplication) getApplication()).getSnApiComponent();\n        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, mLoginFragment).commit();\n        DaggerLoginComponent.builder().loginPresenterModule(new LoginPresenterModule(mLoginFragment))\n                .snApiComponent(snApiComponent).build().inject(this);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            case R.id.menu_login:\n                EasyTracker.getInstance(this).send(MapBuilder.createEvent(\"ui_action\", \"options_item_selected\",\n                        \"loginactivity_menu_login\", 0L).build());\n                mLoginFragment.attemptLogin();\n                return true;\n            default:\n                break;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/MainActivity.java",
    "content": "\npackage com.halzhang.android.apps.startupnews.ui;\n\n\nimport android.content.Intent;\nimport android.content.res.Configuration;\nimport android.os.Bundle;\nimport android.support.customtabs.CustomTabsIntent;\nimport android.support.design.widget.TabLayout;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.app.FragmentManager;\nimport android.support.v4.app.FragmentPagerAdapter;\nimport android.support.v4.view.ViewPager;\nimport android.support.v7.app.ActionBar;\nimport android.support.v7.widget.Toolbar;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.widget.Toast;\n\nimport com.halzhang.android.apps.startupnews.MyApplication;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.presenter.CommentsListPresenter;\nimport com.halzhang.android.apps.startupnews.presenter.CommentsListPresenterModule;\nimport com.halzhang.android.apps.startupnews.presenter.DaggerMainComponent;\nimport com.halzhang.android.apps.startupnews.presenter.MainActivityContract;\nimport com.halzhang.android.apps.startupnews.presenter.MainActivityPresenter;\nimport com.halzhang.android.apps.startupnews.presenter.MainActivityPresenterModule;\nimport com.halzhang.android.apps.startupnews.ui.fragment.CommentsListFragment;\nimport com.halzhang.android.apps.startupnews.ui.fragment.NewsListFragment;\nimport com.halzhang.android.apps.startupnews.ui.fragment.NewsListFragment.OnNewsSelectedListener;\nimport com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;\nimport com.halzhang.android.apps.startupnews.ui.tablet.BrowseFragment;\nimport com.halzhang.android.apps.startupnews.ui.tablet.DiscussFragment;\nimport com.halzhang.android.apps.startupnews.ui.tablet.DiscussFragment.OnMenuSelectedListener;\nimport com.halzhang.android.apps.startupnews.utils.ActivityUtils;\nimport com.halzhang.android.apps.startupnews.utils.AppUtils;\nimport com.halzhang.android.apps.startupnews.utils.CustomTabsActivityHelper;\nimport com.halzhang.android.common.CDLog;\nimport com.halzhang.android.common.CDToast;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\nimport com.halzhang.android.startupnews.data.entity.Status;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Inject;\n\n/**\n * 主页\n *\n * @author Hal\n */\npublic class MainActivity extends BaseFragmentActivity implements OnNewsSelectedListener, OnMenuSelectedListener, MainActivityContract.View {\n\n    private static final String LOG_TAG = MainActivity.class.getSimpleName();\n\n    private static final String TAG_NEWS = \"tag_news\";\n\n    private static final String TAG_NEWEST = \"tag_newest\";\n\n    private static final String TAG_COMMENT = \"tag_comment\";\n\n    private static final String TAG_BROWSE = \"tag_browse\";\n\n    private static final String TAG_DISCUSS = \"tag_discuss\";\n\n    private Intent mFeedbackEmailIntent;\n\n    private SNNew mSnNew;\n    private CustomTabsActivityHelper mHelper;\n\n    private NewsListFragment mHotNewsListFragment;\n    private NewsListFragment mNewsListFragment;\n    private CommentsListFragment mCommentsListFragment;\n\n    @Inject\n    CommentsListPresenter mCommentsListPresenter;\n\n    @Inject\n    MainActivityPresenter mMainActivityPresenter;\n\n    @Inject\n    SessionManager mSessionManager;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        CDLog.i(LOG_TAG, \"MainActivity create!\");\n        super.onCreate(savedInstanceState);\n        if (isFinishing()) {\n            return;\n        }\n        setContentView(R.layout.activity_main);\n        setupViews();\n        mFeedbackEmailIntent = createEmailIntent();\n        mHelper = new CustomTabsActivityHelper();\n\n        SnApiComponent snApiComponent = ((MyApplication) getApplication()).getSnApiComponent();\n        DaggerMainComponent.builder().snApiComponent(snApiComponent)\n                .commentsListPresenterModule(new CommentsListPresenterModule(mCommentsListFragment))\n                .mainActivityPresenterModule(new MainActivityPresenterModule(this))\n                .build().inject(this);\n\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        mHelper.bindService(this);\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        mHelper.unBindService(this);\n    }\n\n    @Override\n    protected void onPostCreate(Bundle savedInstanceState) {\n        super.onPostCreate(savedInstanceState);\n    }\n\n    private void setupViews() {\n\n        mHotNewsListFragment = NewsListFragment.newInstance(getString(R.string.host, \"/news\"));\n        mNewsListFragment = NewsListFragment.newInstance(getString(R.string.host, \"/newest\"));\n        mCommentsListFragment = CommentsListFragment.newInstance();\n\n        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n        final ActionBar ab = getSupportActionBar();\n        if (ab != null) {\n            ab.setDisplayHomeAsUpEnabled(false);\n        }\n        ViewPager viewPager = (ViewPager) findViewById(R.id.pager);\n        if (viewPager != null) {\n            SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());\n            viewPager.setAdapter(sectionsPagerAdapter);\n            TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);\n            tabLayout.setupWithViewPager(viewPager);\n        }\n    }\n\n    private Intent createEmailIntent() {\n        Intent emailIntent = new Intent(Intent.ACTION_SEND);\n        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{\n                \"ghanguo@gmail.com\"\n        });\n        StringBuilder builder = new StringBuilder();\n        builder.append(getString(R.string.app_name)).append(\" v\")\n                .append(AppUtils.getVersionName(getApplicationContext()))\n                .append(getString(R.string.feedback));\n        emailIntent.putExtra(Intent.EXTRA_SUBJECT, builder.toString());\n        emailIntent.setType(\"message/rfc822\");\n        return emailIntent;\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.activity_main, menu);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onPrepareOptionsMenu(Menu menu) {\n        menu.removeItem(R.id.menu_login);\n        menu.removeItem(R.id.menu_logout);\n        if (mSessionManager.isValid()) {\n            menu.add(Menu.NONE, R.id.menu_logout, Menu.NONE, R.string.menu_logout);\n        } else {\n            menu.add(Menu.NONE, R.id.menu_login, Menu.NONE, R.string.menu_login);\n        }\n        return super.onPrepareOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_settings:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                        \"mainactivity_menu_settings\", 0L);\n                startActivity(new Intent(this, AboutActivity.class));\n                return true;\n            case R.id.menu_feedback:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                        \"mainactivity_menu_feedback\", 0L);\n                if (ActivityUtils.isIntentAvailable(getApplicationContext(), mFeedbackEmailIntent)) {\n                    startActivity(mFeedbackEmailIntent);\n                } else {\n                    Toast.makeText(getApplicationContext(), R.string.error_noemailapp,\n                            Toast.LENGTH_LONG).show();\n                }\n                return true;\n            case R.id.menu_login: {\n                Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                        \"mainactivity_menu_login\", 0L);\n                Intent intent = new Intent(this, LoginActivity.class);\n                startActivity(intent);\n            }\n            return true;\n            case R.id.menu_logout:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                        \"mainactivity_menu_logout\", 0L);\n                mSessionManager.clear();\n                CDToast.showToast(this, R.string.tip_logout_success);\n                mMainActivityPresenter.logout();\n                return true;\n            case R.id.menu_show_comment:\n                showDiscussFragment();\n                return true;\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n    }\n\n    private void showDiscussFragment() {\n        showDiscussFragment(mSnNew, null);\n    }\n\n    private void showDiscussFragment(SNNew snNew, String discussUrl) {\n        Bundle args = new Bundle();\n        if (snNew != null) {\n            args.putParcelable(DiscussActivity.ARG_SNNEW, snNew);\n            args.putString(DiscussActivity.ARG_DISCUSS_URL, mSnNew.getDiscussURL());\n        } else {\n            args.putString(DiscussActivity.ARG_DISCUSS_URL, discussUrl);\n        }\n        DiscussFragment fragment = new DiscussFragment();\n        fragment.setArguments(args);\n        getSupportFragmentManager().beginTransaction()\n                .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)\n                .replace(R.id.fragment_container, fragment, TAG_DISCUSS).commit();\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        super.onConfigurationChanged(newConfig);\n    }\n\n    @Override\n    public void onLogoutResult(boolean result) {\n        CDToast.showToast(getApplicationContext(), result ? R.string.tip_logout_success\n                : R.string.tip_logout_failure);\n    }\n\n    @Override\n    public void onUpVoteFailure(Throwable e) {\n        Tracker.getInstance().sendException(\"up vote error!\", e, false);\n        CDToast.showToast(this, getString(R.string.tip_vote_failure));\n    }\n\n    @Override\n    public void onUpVoteSuccess(Status status) {\n        switch (status.code) {\n            case Status.CODE_COOKIE_VALID:\n                startActivity(new Intent(this, LoginActivity.class));\n                CDToast.showToast(this, R.string.tip_cookie_invalid);\n                break;\n            case Status.CODE_REPEAT:\n                CDToast.showToast(this, getString(R.string.tip_vote_duplicate));\n                break;\n            case Status.CODE_SUCCESS:\n                CDToast.showToast(this, R.string.tip_vote_success);\n                break;\n            default:\n                break;\n        }\n    }\n\n    /* no-op */\n    @Override\n    public void setPresenter(MainActivityContract.Presenter presenter) {\n\n    }\n\n    @Override\n    public boolean isActive() {\n        return !isFinishing();\n    }\n\n    private class SectionsPagerAdapter extends FragmentPagerAdapter {\n\n        private String[] titles;\n\n        public SectionsPagerAdapter(FragmentManager fm) {\n            super(fm);\n            titles = getResources().getStringArray(R.array.section_titles);\n        }\n\n        @Override\n        public Fragment getItem(int arg0) {\n            switch (arg0) {\n                case 0:\n                    return mHotNewsListFragment;\n                case 1:\n                    return mNewsListFragment;\n                case 2:\n                    return mCommentsListFragment;\n                default:\n                    throw new IllegalArgumentException();\n            }\n        }\n\n        @Override\n        public int getCount() {\n            return titles.length;\n        }\n\n        @Override\n        public CharSequence getPageTitle(int position) {\n            return titles[position];\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return super.getItemId(position);\n        }\n\n    }\n\n    @Override\n    public void onNewsSelected(int position, final SNNew snNew) {\n        mSnNew = snNew;\n        CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(mHelper.getSession());\n        //设置 toolbar 颜色\n        builder.setToolbarColor(0XFF33B5E5);\n        CustomTabsIntent customTabsIntent = builder.build();\n        mHelper.launchUrl(this, snNew.getUrl(), customTabsIntent, new CustomTabsActivityHelper.OnCustomTabsInvalidListener() {\n            @Override\n            public void onInvalid(String url) {\n                ActivityUtils.openArticle(MainActivity.this, snNew);\n            }\n        });\n    }\n\n    private void showBrowseFragment(SNNew snNew) {\n        BrowseFragment browseFragment = (BrowseFragment) getSupportFragmentManager()\n                .findFragmentByTag(TAG_BROWSE);\n        if (browseFragment != null) {\n            browseFragment.setTitle(snNew.getTitle());\n            browseFragment.load(snNew.getUrl());\n        } else {\n            BrowseFragment fragment = new BrowseFragment();\n            Bundle bundle = new Bundle();\n            bundle.putString(BrowseActivity.EXTRA_URL, snNew.getUrl());\n            bundle.putString(BrowseActivity.EXTRA_TITLE, snNew.getTitle());\n            fragment.setArguments(bundle);\n            getSupportFragmentManager().beginTransaction()\n                    .setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right)\n                    .replace(R.id.fragment_container, fragment, TAG_BROWSE).commit();\n        }\n    }\n\n\n    @Override\n    public void onShowArticleSelected(SNNew snNew) {\n        mSnNew = snNew;\n        showBrowseFragment(snNew);\n    }\n\n    @Override\n    public void onUpVoteSelected(String postId) {\n        mMainActivityPresenter.upVote(postId);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/ShareHelper.java",
    "content": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\npackage com.halzhang.android.apps.startupnews.ui;\n\n\n/**\n * StartupNews\n * <p>\n * </p>\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Jul 7, 2013\n */\npublic class ShareHelper {\n    //TODO implements\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/SubmitActivity.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.ui;\n\nimport android.support.v4.app.FragmentActivity;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 11, 2013\n */\npublic class SubmitActivity extends FragmentActivity {\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/BaseFragment.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\n\nimport rx.subscriptions.CompositeSubscription;\n\n/**\n * Created by Hal on 16/6/18.\n */\npublic class BaseFragment extends Fragment {\n\n    protected CompositeSubscription mCompositeSubscription;\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mCompositeSubscription = new CompositeSubscription();\n    }\n\n    @Override\n    public void onDestroyView() {\n        mCompositeSubscription.clear();\n        super.onDestroyView();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/CommentsListFragment.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.v7.widget.RecyclerView;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.presenter.CommentsListContract;\nimport com.halzhang.android.apps.startupnews.ui.DiscussActivity;\nimport com.halzhang.android.startupnews.data.entity.SNComment;\nimport com.halzhang.android.startupnews.data.entity.SNComments;\n\nimport java.util.ArrayList;\n\n/**\n * StartupNews\n * <p>\n * 评论\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 7, 2013\n */\npublic class CommentsListFragment extends SwipeRefreshRecyclerFragment implements CommentsListContract.View {\n\n    private static final String LOG_TAG = CommentsListFragment.class.getSimpleName();\n\n    // private ArrayList<SNComment> mComments = new ArrayList<SNComment>(24);\n\n    private CommentsListContract.Presenter mPresenter;\n\n    private CommentsAdapter mAdapter;\n\n\n    private SNComments mSnComments = new SNComments();\n\n    private static final String NEWCOMMENTS_URL_PATH = \"/newcomments\";\n\n    public CommentsListFragment() {\n    }\n\n    public static CommentsListFragment newInstance() {\n        return new CommentsListFragment();\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mAdapter = new CommentsAdapter();\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n    }\n\n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        mRecyclerView.setAdapter(mAdapter);\n        if (mAdapter.isEmpty()) {\n            mPresenter.getComments(getString(R.string.host, NEWCOMMENTS_URL_PATH));\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n    }\n\n    @Override\n    protected void onRefreshData() {\n        super.onRefreshData();\n        Tracker.getInstance().sendEvent(\"ui_action\", \"pull_down_list_view_refresh\",\n                \"comments_list_fragment_pull_down_list_view_refresh\", 0L);\n        mPresenter.getComments(getString(R.string.host, NEWCOMMENTS_URL_PATH));\n\n    }\n\n    @Override\n    protected void onLoadMore() {\n        super.onLoadMore();\n        Tracker.getInstance().sendEvent(\"ui_action\", \"pull_up_list_view_refresh\",\n                \"comments_list_fragment_pull_up_list_view_refresh\", 0L);\n        mPresenter.getMoreComments();\n    }\n\n    @Override\n    public void onSuccess(ArrayList<SNComment> snComments) {\n        onRefreshComplete();\n        mSnComments.setSnComments(snComments);\n        mAdapter.notifyDataSetChanged();\n    }\n\n    @Override\n    public void onFailure(Throwable e) {\n        Log.e(LOG_TAG, \"\", e);\n        onRefreshComplete();\n        Tracker.getInstance().sendException(\"CommentsTask\", e, false);\n        Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_LONG).show();\n    }\n\n    @Override\n    public void onAtEnd() {\n        onRefreshComplete();\n        Toast.makeText(getActivity(), R.string.tip_last_page, Toast.LENGTH_SHORT).show();\n    }\n\n    @Override\n    public void setPresenter(CommentsListContract.Presenter presenter) {\n        mPresenter = presenter;\n    }\n\n    @Override\n    public boolean isActive() {\n        return isAdded();\n    }\n\n    private class CommentsAdapter extends RecyclerView.Adapter<CommentsAdapter.ViewHolder> {\n\n        public final class ViewHolder extends RecyclerView.ViewHolder {\n\n            public final TextView mUserId;\n\n            public final TextView mCreated;\n\n            public final TextView mCommentText;\n\n            public final TextView mArtistTitle;\n\n            public final View mItemView;\n\n            public ViewHolder(View itemView) {\n                super(itemView);\n                mItemView = itemView;\n                mUserId = (TextView) itemView.findViewById(R.id.comment_item_user_id);\n                mCreated = (TextView) itemView.findViewById(R.id.comment_item_created);\n                mCommentText = (TextView) itemView.findViewById(R.id.comment_item_text);\n                mArtistTitle = (TextView) itemView.findViewById(R.id.comment_item_artist_titile);\n            }\n        }\n\n        @Override\n        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n            return new ViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.comment_list_item, null));\n        }\n\n        @Override\n        public void onBindViewHolder(ViewHolder holder, int position) {\n            SNComment comment = mSnComments.getSnComments().get(position);\n            holder.mUserId.setText(comment.getUser().getId());\n            holder.mCreated.setText(comment.getCreated());\n            holder.mCommentText.setText(comment.getText());\n            holder.mArtistTitle.setText(getString(R.string.comment_artist_title, comment.getArtistTitle()));\n            holder.mItemView.setTag(position);\n            holder.mItemView.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    Tracker.getInstance().sendEvent(\"ui_action\", \"list_item_click\", \"comments_list_fragment_list_item_click\", 0L);\n                    int position = (int) v.getTag();\n                    SNComment comment = mSnComments.getSnComments().get(position);\n                    Intent intent = new Intent(getActivity(), DiscussActivity.class);\n                    intent.putExtra(DiscussActivity.ARG_DISCUSS_URL, comment.getDiscussURL());\n                    startActivity(intent);\n                }\n            });\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n\n        @Override\n        public int getItemCount() {\n            return mSnComments.size();\n        }\n\n        public boolean isEmpty() {\n            return getItemCount() == 0;\n        }\n    }\n\n    @Override\n    public int getViewLayout() {\n        return R.layout.refresh_recycler_view_layout;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/LoginFragment.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.fragment;\n\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapter;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.v4.app.Fragment;\nimport android.text.TextUtils;\nimport android.view.KeyEvent;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.inputmethod.EditorInfo;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.halzhang.android.apps.startupnews.Constants;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.presenter.LoginContract;\nimport com.halzhang.android.common.CDToast;\n\nimport rx.Subscription;\n\n/**\n * A simple {@link Fragment} subclass.\n * Use the {@link LoginFragment#newInstance} factory method to\n * create an instance of this fragment.\n */\npublic class LoginFragment extends BaseFragment implements LoginContract.View {\n\n    // UI references.\n    private EditText mUsernameView;\n\n    private EditText mPasswordView;\n\n    private View mLoginFormView;\n\n    private View mLoginStatusView;\n\n    private TextView mLoginStatusMessageView;\n\n    private LoginContract.Presenter mPresenter;\n\n    public LoginFragment() {\n\n    }\n\n    public static LoginFragment newInstance() {\n        return new LoginFragment();\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    @Override\n    public void onAttach(Context activity) {\n        super.onAttach(activity);\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View view = inflater.inflate(R.layout.fragment_login, null);\n        mUsernameView = (EditText) view.findViewById(R.id.username);\n        mPasswordView = (EditText) view.findViewById(R.id.password);\n        mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {\n            @Override\n            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {\n                if (id == R.id.login || id == EditorInfo.IME_NULL) {\n                    attemptLogin();\n                    return true;\n                }\n                return false;\n            }\n        });\n\n        mLoginFormView = view.findViewById(R.id.login_form);\n        mLoginStatusView = view.findViewById(R.id.login_status);\n        mLoginStatusMessageView = (TextView) view.findViewById(R.id.login_status_message);\n        mLoginStatusMessageView.setText(R.string.login_progress_init);\n        showProgress(false);\n        Button loginBtn = (Button) view.findViewById(R.id.btn_login);\n        loginBtn.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                attemptLogin();\n            }\n        });\n        return view;\n    }\n\n    /**\n     * Attempts to sign in or register the account specified by the login\n     * form. If there are form errors (invalid email, missing fields, etc.),\n     * the errors are presented and no actual login attempt is made.\n     */\n    public void attemptLogin() {\n        // Reset errors.\n        mUsernameView.setError(null);\n        mPasswordView.setError(null);\n\n        String username = mUsernameView.getText().toString();\n        String password = mPasswordView.getText().toString();\n\n        boolean cancel = false;\n        View focusView = null;\n\n        if (TextUtils.isEmpty(password)) {\n            mPasswordView.setError(getString(R.string.error_field_required));\n            focusView = mPasswordView;\n            cancel = true;\n        }\n        if (TextUtils.isEmpty(username)) {\n            mUsernameView.setError(getString(R.string.error_field_required));\n            focusView = mUsernameView;\n            cancel = true;\n        }\n\n        if (cancel) {\n            focusView.requestFocus();\n        } else {\n            mLoginStatusMessageView.setText(R.string.login_progress_signing_in);\n            showProgress(true);\n            mPresenter.login(username, password);\n        }\n    }\n\n    /**\n     * Shows the progress UI and hides the login form.\n     */\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)\n    public void showProgress(final boolean show) {\n        if (getActivity() == null) {\n            //防止 not attact to activity 出错\n            return;\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {\n            int shortAnimTime = getResources().getInteger(\n                    android.R.integer.config_shortAnimTime);\n\n            mLoginStatusView.setVisibility(View.INVISIBLE);\n            mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0)\n                    .setListener(new AnimatorListenerAdapter() {\n                        @Override\n                        public void onAnimationEnd(Animator animation) {\n                            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);\n                        }\n                    });\n\n            mLoginFormView.setVisibility(View.INVISIBLE);\n            mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1)\n                    .setListener(new AnimatorListenerAdapter() {\n                        @Override\n                        public void onAnimationEnd(Animator animation) {\n                            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);\n                        }\n                    });\n        } else {\n            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);\n            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);\n        }\n    }\n\n\n    @Override\n    public void onLoginError(Throwable e) {\n        e.printStackTrace();\n    }\n\n    @Override\n    public void onLoginResult(String user) {\n        Activity activity = getActivity();\n        if (activity == null || activity.isFinishing()) {\n            return;\n        }\n        showProgress(false);\n        if (!TextUtils.isEmpty(user)) {\n            CDToast.showToast(activity, R.string.tip_login_success);\n            Intent intent = new Intent(Constants.IntentAction.ACTION_LOGIN);\n            intent.putExtra(Constants.IntentAction.EXTRA_LOGIN_USER, user);\n            activity.sendBroadcast(intent);\n            activity.finish();\n        } else {\n            CDToast.showToast(activity, R.string.tip_login_failure);\n        }\n    }\n\n    @Override\n    public void addSubscription(Subscription subscription) {\n        mCompositeSubscription.add(subscription);\n    }\n\n    @Override\n    public void setPresenter(LoginContract.Presenter presenter) {\n        mPresenter = presenter;\n    }\n\n    @Override\n    public boolean isActive() {\n        return isAdded();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/NewsListFragment.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.app.Activity;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.graphics.Color;\nimport android.os.Bundle;\nimport android.support.v7.widget.RecyclerView;\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.view.ContextMenu;\nimport android.view.ContextMenu.ContextMenuInfo;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView.AdapterContextMenuInfo;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.halzhang.android.apps.startupnews.Constants.IntentAction;\nimport com.halzhang.android.apps.startupnews.MyApplication;\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.SnApiComponent;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.presenter.DaggerNewsListFragmentComponent;\nimport com.halzhang.android.apps.startupnews.presenter.NewsListContract;\nimport com.halzhang.android.apps.startupnews.presenter.NewsListPresenter;\nimport com.halzhang.android.apps.startupnews.presenter.NewsListPresenterModule;\nimport com.halzhang.android.apps.startupnews.ui.DiscussActivity;\nimport com.halzhang.android.apps.startupnews.utils.AppUtils;\nimport com.halzhang.android.common.CDLog;\nimport com.halzhang.android.startupnews.data.entity.SNFeed;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\n\nimport java.util.ArrayList;\n\nimport javax.inject.Inject;\n\n/**\n * StartupNews\n * <p>\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 7, 2013\n */\npublic class NewsListFragment extends SwipeRefreshRecyclerFragment implements NewsListContract.View {\n\n    private static final String LOG_TAG = NewsListFragment.class.getSimpleName();\n\n    @Inject\n    NewsListPresenter mNewsListPresenter;\n\n    private NewsListContract.Presenter mPresenter;\n\n    @Override\n    public void setPresenter(NewsListContract.Presenter presenter) {\n        mPresenter = presenter;\n    }\n\n    @Override\n    public boolean isActive() {\n        return isAdded();\n    }\n\n    @Override\n    public void onSuccess(ArrayList<SNNew> snNews) {\n        onRefreshComplete();\n        mSnFeed.setSnNews(snNews);\n        mAdapter.notifyDataSetChanged();\n    }\n\n    @Override\n    public void onFailure(Throwable e) {\n        CDLog.w(LOG_TAG, \"\", e);\n        onRefreshComplete();\n        Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_LONG).show();\n        Tracker.getInstance().sendException(\"NewsTask\", e, false);\n    }\n\n    @Override\n    public void onAtEnd() {\n        onRefreshComplete();\n        Toast.makeText(getActivity(), R.string.tip_last_page, Toast.LENGTH_SHORT).show();\n    }\n\n    /**\n     * {@link NewsListFragment}选中监听器\n     */\n    public interface OnNewsSelectedListener {\n        /**\n         * 处理news被选中事件\n         *\n         * @param position list position\n         * @param snNew    {@link SNNew}\n         */\n        public void onNewsSelected(int position, SNNew snNew);\n    }\n\n    private OnNewsSelectedListener mNewsSelectedListener;\n\n\n    private String mNewsURL;\n\n    public static final String ARG_URL = \"new_url\";\n\n    private SNFeed mSnFeed = new SNFeed();\n\n    private NewsAdapter mAdapter;\n\n    public NewsListFragment() {\n\n    }\n\n    public static NewsListFragment newInstance(String url) {\n        NewsListFragment fragment = new NewsListFragment();\n        Bundle args = new Bundle(1);\n        args.putString(ARG_URL, url);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    private BroadcastReceiver mReceiver = new BroadcastReceiver() {\n\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            final String action = intent.getAction();\n            if (IntentAction.ACTION_LOGIN.equals(action)) {\n                String user = intent.getStringExtra(IntentAction.EXTRA_LOGIN_USER);\n                if (!TextUtils.isEmpty(user)) {\n                    mPresenter.getFeed(mNewsURL);\n                }\n            }\n        }\n    };\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        try {\n            mNewsSelectedListener = (OnNewsSelectedListener) activity;\n        } catch (ClassCastException e) {\n            throw new ClassCastException(activity.toString()\n                    + \" must implement OnNewsSelectedListener\");\n        }\n    }\n\n    @Override\n    public void onDetach() {\n        super.onDetach();\n        mNewsSelectedListener = null;\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mAdapter = new NewsAdapter();\n        Bundle args = getArguments();\n        if (args != null) {\n            mNewsURL = args.getString(ARG_URL);\n        } else {\n            mNewsURL = getString(R.string.host, \"/news\");\n        }\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(IntentAction.ACTION_LOGIN);\n        getActivity().registerReceiver(mReceiver, filter);\n        SnApiComponent snApiComponent = ((MyApplication) getActivity().getApplication()).getSnApiComponent();\n        DaggerNewsListFragmentComponent.builder().snApiComponent(snApiComponent)\n                .newsListPresenterModule(new NewsListPresenterModule(this)).build().inject(this);\n    }\n\n    @Override\n    protected int getViewLayout() {\n        return R.layout.refresh_recycler_view_layout;\n    }\n\n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n        mRecyclerView.setAdapter(mAdapter);\n        if (mAdapter.isEmpty()) {\n            mPresenter.getFeed(mNewsURL);\n            mSwipeRefreshLayout.post(new Runnable() {\n                @Override\n                public void run() {\n                    mSwipeRefreshLayout.setRefreshing(true);\n                }\n            });\n        }\n    }\n\n    @Override\n    public void onDestroyView() {\n        super.onDestroyView();\n        CDLog.d(LOG_TAG, this.toString() + \" destroy view!\");\n    }\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        getActivity().unregisterReceiver(mReceiver);\n    }\n\n    @Override\n    public boolean onContextItemSelected(MenuItem item) {\n        int position = ((AdapterContextMenuInfo) item.getMenuInfo()).position;\n        final SNNew snNew = mSnFeed.getSnNews().get(position);\n        Log.i(LOG_TAG, snNew.toString());\n        switch (item.getItemId()) {\n            case R.id.menu_show_comment:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"context_item_selected\",\n                        \"newslistfragment_menu_show_comment\", 0L);\n                openDiscuss(snNew);\n                return true;\n            case R.id.menu_show_article:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"context_item_selected\",\n                        \"newslistfragment_menu_show_acticle\", 0L);\n                openArticle(position - 1, snNew);\n                return true;\n            case R.id.menu_up_vote:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"context_item_selected\",\n                        \"newslistfragment_menu_upvote\", 0L);\n\n                // TODO: 16/8/14 投票操作\n\n                return true;\n            default:\n                break;\n        }\n        return true;\n    }\n\n    @Override\n    protected void onRefreshData() {\n        super.onRefreshData();\n        Tracker.getInstance().sendEvent(\"ui_action\", \"pull_down_list_view_refresh\", \"news_list_fragment_pull_down_list_view_refresh\", 0L);\n        mPresenter.getFeed(mNewsURL);\n    }\n\n    @Override\n    protected void onLoadMore() {\n        super.onLoadMore();\n        Tracker.getInstance().sendEvent(\"ui_action\", \"pull_up_list_view_refresh\", \"news_list_fragment_pull_up_list_view_refresh\", 0L);\n\n        mPresenter.getMoreFeed();\n    }\n\n    private void openArticle(int position, SNNew snNew) {\n        if (mNewsSelectedListener != null) {\n            mNewsSelectedListener.onNewsSelected(position, snNew);\n        }\n    }\n\n    private void openDiscuss(SNNew snNew) {\n        if (snNew == null) {\n            return;\n        }\n\n        DiscussActivity.start(getActivity(),snNew.getDiscussURL(),snNew);\n    }\n\n    private class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {\n\n        public final class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {\n\n            public final TextView user;\n\n            public final TextView createAt;\n\n            public final TextView title;\n\n            public final TextView subText;\n\n            public final TextView domain;\n\n            public final View mView;\n\n            public ViewHolder(View itemView) {\n                super(itemView);\n                mView = itemView;\n                user = (TextView) itemView.findViewById(R.id.news_item_user);\n                createAt = (TextView) itemView.findViewById(R.id.news_item_createat);\n                title = (TextView) itemView.findViewById(R.id.news_item_title);\n                subText = (TextView) itemView.findViewById(R.id.news_item_subtext);\n                domain = (TextView) itemView.findViewById(R.id.news_item_domain);\n                mView.setOnCreateContextMenuListener(this);\n                mView.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        int position = (int) v.getTag();\n                        SNNew entity = mSnFeed.getSnNews().get(position);\n                        Tracker.getInstance().sendEvent(\"ui_action\", \"list_item_click\",\n                                \"news_list_fragment_list_item_click\", 0L);\n                        mAdapter.notifyDataSetChanged();\n                        openArticle(position, entity);\n                    }\n                });\n            }\n\n            @Override\n            public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {\n                getActivity().getMenuInflater().inflate(R.menu.fragment_news, menu);\n            }\n        }\n\n\n        @Override\n        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n            return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.news_list_item, null));\n        }\n\n        @Override\n        public void onBindViewHolder(ViewHolder holder, int position) {\n            final SNNew entity = mSnFeed.getSnNews().get(position);\n            holder.user.setText(entity.getUser().getId());\n            holder.title.setText(entity.getTitle());\n            holder.subText.setText(getString(R.string.news_subtext, entity.getPoints(),\n                    entity.getCommentsCount()));\n            holder.createAt.setText(entity.getCreateat());\n            holder.domain.setText(entity.getUrlDomain());\n            int textColor = AppUtils.getMyApplication(getActivity()).isHistoryContains(\n                    entity.getUrl()) ? Color.GRAY : Color.BLACK;\n            holder.title.setTextColor(textColor);\n            holder.subText.setTextColor(textColor);\n            holder.domain.setTextColor(textColor);\n            holder.createAt.setTextColor(textColor);\n            holder.mView.setTag(position);\n        }\n\n        @Override\n        public int getItemCount() {\n            return mSnFeed.getSnNews().size();\n        }\n\n        public boolean isEmpty() {\n            return getItemCount() == 0;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/SwipeRefreshRecyclerFragment.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.fragment;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.widget.SwipeRefreshLayout;\nimport android.support.v7.widget.LinearLayoutManager;\nimport android.support.v7.widget.RecyclerView;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.ui.widgets.CardViewDividerDecoration;\nimport com.halzhang.android.apps.startupnews.ui.widgets.DividerDecoration;\n\npublic abstract class SwipeRefreshRecyclerFragment extends Fragment {\n\n    private static final String LOG_TAG = SwipeRefreshRecyclerFragment.class.getSimpleName();\n\n    protected SwipeRefreshLayout mSwipeRefreshLayout;\n    protected RecyclerView mRecyclerView;\n    private LinearLayoutManager mLinearLayoutManager;\n\n\n    public SwipeRefreshRecyclerFragment() {\n        // Required empty public constructor\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n    }\n\n    protected abstract int getViewLayout();\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        View view = inflater.inflate(getViewLayout(), null);\n        mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);\n        mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_layout);\n        if (mRecyclerView == null || mSwipeRefreshLayout == null) {\n            throw new IllegalArgumentException(\"mush be have RecyclerView and SwipeRefreshLayout\");\n        }\n        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);\n        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {\n            @Override\n            public void onRefresh() {\n                onRefreshData();\n            }\n        });\n        mLinearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);\n        mRecyclerView.setLayoutManager(mLinearLayoutManager);\n        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {\n            @Override\n            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {\n                super.onScrollStateChanged(recyclerView, newState);\n            }\n\n            @Override\n            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {\n                super.onScrolled(recyclerView, dx, dy);\n                boolean enable = false;\n                int visibleItemCount = mRecyclerView.getChildCount();\n                int itemCount = mLinearLayoutManager.getItemCount();\n                int firstVisibleItemPosition = mLinearLayoutManager.findFirstVisibleItemPosition();\n                int lastVisibleItemPosition = mLinearLayoutManager.findLastVisibleItemPosition();\n//                if (visibleItemCount > 0) {\n//                    boolean firstItemVisible = firstVisibleItemPosition == 0;\n//                    boolean topOfFirstItemVisible = mLinearLayoutManager.getChildAt(0).getTop() == 0;\n//                    enable = firstItemVisible && topOfFirstItemVisible;\n//                    Log.d(LOG_TAG, \"SwipeRefreshLayout enable: \" + enable);\n//                }\n//                mSwipeRefreshLayout.setEnabled(enable);\n                if (lastVisibleItemPosition == itemCount - 1) {\n                    onLoadMore();\n                }\n            }\n        });\n        mRecyclerView.addItemDecoration(new CardViewDividerDecoration(getActivity()));\n        return view;\n    }\n\n    /**\n     * 刷新数据\n     */\n    protected void onRefreshData() {\n    }\n\n    /**\n     * 加载更多\n     */\n    protected void onLoadMore() {\n    }\n\n    protected void onRefreshComplete() {\n        if (mSwipeRefreshLayout != null) {\n            mSwipeRefreshLayout.setRefreshing(false);\n        }\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n    }\n\n    @Override\n    public void onDetach() {\n        super.onDetach();\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/phone/BrowseActivity.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.ui.phone;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;\nimport com.halzhang.android.apps.startupnews.ui.tablet.BrowseFragment;\n\nimport android.annotation.TargetApi;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.v7.app.ActionBar;\nimport android.text.TextUtils;\nimport android.view.MenuItem;\nimport android.view.Window;\n\n/**\n * StartupNews\n * <p>\n * 浏览页面\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 7, 2013\n */\npublic class BrowseActivity extends BaseFragmentActivity {\n\n    // private static final String LOG_TAG =\n    // BrowseActivity.class.getSimpleName();\n\n    public static final String EXTRA_URL = \"extra_url\";\n\n    public static final String EXTRA_TITLE = \"extra_title\";\n\n    @Override\n    protected void onCreate(Bundle arg0) {\n        super.onCreate(arg0);\n        setContentView(R.layout.activity_browse);\n        String mOriginalUrl = getIntent().getStringExtra(EXTRA_URL);\n        if (TextUtils.isEmpty(mOriginalUrl)) {\n            finish();\n            return;\n        }\n        String mTitle = getIntent().getStringExtra(EXTRA_TITLE);\n\n        final ActionBar actionBar = getSupportActionBar();\n        if (actionBar != null) {\n            actionBar.setDisplayShowTitleEnabled(true);\n            actionBar.setDisplayHomeAsUpEnabled(true);\n        }\n\n        BrowseFragment fragment = new BrowseFragment();\n        Bundle bundle = new Bundle();\n        bundle.putString(EXTRA_URL, mOriginalUrl);\n        bundle.putString(EXTRA_TITLE, mTitle);\n        fragment.setArguments(bundle);\n        getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragment)\n                .commitAllowingStateLoss();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/BrowseFragment.java",
    "content": "\npackage com.halzhang.android.apps.startupnews.ui.tablet;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.view.MenuCompat;\nimport android.support.v4.view.MenuItemCompat;\nimport android.support.v7.widget.ShareActionProvider;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;\nimport com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;\nimport com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView;\nimport com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView.OnScrollChangedCallback;\nimport com.halzhang.android.apps.startupnews.ui.widgets.WebViewController;\nimport com.halzhang.android.apps.startupnews.utils.PreferenceUtils;\nimport com.halzhang.android.common.CDLog;\n\n/**\n * 浏览器 Created by Hal on 13-5-25.\n */\npublic class BrowseFragment extends Fragment implements OnScrollChangedCallback {\n\n    private static final String LOG_TAG = BrowseFragment.class.getSimpleName();\n\n    private ObservableWebView mWebView;\n\n    private WebViewController mWebViewController;\n\n    private String mTitle;\n\n    private String mOriginalUrl;\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n    }\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mWebViewController = new WebViewController(getActivity());\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        setHasOptionsMenu(true);\n        final View view = inflater.inflate(R.layout.fragment_browse, null);\n        mWebView = (ObservableWebView) view.findViewById(R.id.webview);\n        Activity activity = getActivity();\n        mWebView.setOverScrollMode(View.OVER_SCROLL_NEVER);\n        mWebView.setOnScrollChangedCallback(this);\n        mWebViewController.initControllerView(mWebView, view);\n        Bundle args = getArguments();\n        if (args != null && args.containsKey(BrowseActivity.EXTRA_TITLE)\n                && args.containsKey(BrowseActivity.EXTRA_URL)) {\n            mTitle = args.getString(BrowseActivity.EXTRA_TITLE);\n            mOriginalUrl = args.getString(BrowseActivity.EXTRA_URL);\n            String mHtmlProvider = PreferenceUtils.getHtmlProvider(activity);\n            final String url = mHtmlProvider + mOriginalUrl;\n            mWebViewController.loadUrl(url);\n        }\n        return view;\n    }\n\n    @Override\n    public void onDestroy() {\n        mWebViewController.destroy();\n        super.onDestroy();\n    }\n\n    public void setTitle(String title) {\n        mTitle = title;\n    }\n\n    public void load(String url) {\n        mOriginalUrl = url;\n        String mHtmlProvider = PreferenceUtils.getHtmlProvider(getActivity());\n        mWebViewController.loadUrl(mHtmlProvider + mOriginalUrl);\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        inflater.inflate(R.menu.fragment_browse, menu);\n        super.onCreateOptionsMenu(menu, inflater);\n        MenuItem actionItem = menu.findItem(R.id.menu_share);\n        ShareActionProvider actionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(actionItem);\n        if (actionProvider == null) {\n            return;\n        }\n        actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME);\n        actionProvider.setShareIntent(createShareIntent());\n        actionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {\n\n            @Override\n            public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {\n                /*\n                 * 这里改变intent是没用的，intent只是一份拷贝，只能自己启动修改后的Intent\n                 * 然而自己启动Intent并不会改变历史记录 增加setAllowPolicyChangeIntent方法解决这问题\n                 */\n                String packageName = intent.getComponent().getPackageName();\n                CDLog.i(LOG_TAG, packageName);\n                String shareContent = getShareContent();\n                intent.putExtra(Intent.EXTRA_SUBJECT, mTitle);\n                Tracker.getInstance().sendEvent(\"ui_action\", \"share\", packageName, 0L);\n                if (getString(R.string.weibo_package_name).equals(packageName)) {\n                    intent.putExtra(Intent.EXTRA_TEXT, shareContent + \" \"\n                            + getString(R.string.weibo_share_suffix));\n                } else {\n                    intent.putExtra(Intent.EXTRA_TEXT, shareContent);\n                }\n                return false;\n            }\n        });\n    }\n\n    /**\n     * Creates a sharing {@link Intent}.\n     *\n     * @return The sharing intent.\n     */\n    private Intent createShareIntent() {\n        Intent intent = new Intent(Intent.ACTION_SEND);\n        intent.setType(\"text/plain\");\n        intent.putExtra(Intent.EXTRA_SUBJECT, mTitle);\n        intent.putExtra(Intent.EXTRA_TEXT, getShareContent());\n        return intent;\n    }\n\n    private String getShareContent() {\n        StringBuilder builder = new StringBuilder();\n        return builder.append(mTitle).append(\" \").append(mOriginalUrl).toString();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_original_url:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\", \"browseactivity_menu_original_url\", 0L);\n                mWebViewController.loadUrl(mOriginalUrl);\n                return true;\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n    }\n\n    @Override\n    public void onScroll(int l, int t, int oldl, int oldt) {\n\n    }\n\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    @Override\n    public void onScrollUp() {\n        //TODO 需要判断 Activity 的类型\n        BaseFragmentActivity activity = (BaseFragmentActivity) getActivity();\n        activity.getSupportActionBar().show();\n        mWebViewController.showBrowseBar();\n    }\n\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    @Override\n    public void onScrollDown() {\n        BaseFragmentActivity activity = (BaseFragmentActivity) getActivity();\n        activity.getSupportActionBar().hide();\n        mWebViewController.hideBrowseBar();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/DiscussFragment.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.tablet;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.v4.app.Fragment;\nimport android.text.Editable;\nimport android.text.Html;\nimport android.text.TextWatcher;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.view.ViewGroup;\nimport android.widget.AdapterView;\nimport android.widget.AdapterView.OnItemClickListener;\nimport android.widget.BaseAdapter;\nimport android.widget.EditText;\nimport android.widget.ImageButton;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.presenter.DiscussContract;\nimport com.halzhang.android.apps.startupnews.ui.LoginActivity;\nimport com.halzhang.android.apps.startupnews.utils.ActivityUtils;\nimport com.halzhang.android.common.CDToast;\nimport com.halzhang.android.startupnews.data.entity.SNComment;\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\nimport com.halzhang.android.startupnews.data.entity.Status;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Inject;\n\n/**\n * 查看评论\n * Created by Hal on 13-5-26.\n */\npublic class DiscussFragment extends Fragment implements OnItemClickListener, DiscussContract.View {\n\n    public static final String ARG_DISCUSS_URL = \"discuss_url\";\n    public static final String ARG_SNNEW = \"snnew\";\n\n    private static final String LOG_TAG = DiscussFragment.class.getSimpleName();\n\n\n    private DiscussContract.Presenter mPresenter;\n\n    @Override\n    public void setPresenter(DiscussContract.Presenter presenter) {\n        mPresenter = presenter;\n    }\n\n    @Override\n    public boolean isActive() {\n        return isAdded();\n    }\n\n    @Override\n    public void onGetDiscuss(SNDiscuss snDiscuss) {\n        mSnDiscuss.clearComments();\n        mSnDiscuss.copy(snDiscuss);\n        setRefreshActionButtonState(false);\n        wrapHeaderView(mSnDiscuss.getSnNew());\n        mAdapter.notifyDataSetChanged();\n    }\n\n    @Override\n    public void onGetDiscussFailure(Throwable e) {\n        Tracker.getInstance().sendException(\"DiscussTask\", e, false);\n        Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_SHORT).show();\n    }\n\n    @Override\n    public void onCommentSuccess(Status status) {\n\n        switch (status.code){\n            case Status.CODE_COOKIE_VALID:\n                CDToast.showToast(getActivity(), R.string.tip_cookie_invalid);\n                startActivity(new Intent(getActivity(), LoginActivity.class));\n                Tracker.getInstance().sendEvent(\"ui_action_feedback\",\n                        \"comment_feedback\", getString(R.string.tip_cookie_invalid),\n                        0L);\n                break;\n            case Status.CODE_SUCCESS:\n                mCommentEdit.setText(null);\n                CDToast.showToast(getActivity(), R.string.tip_comment_success);\n                Tracker.getInstance().sendEvent(\"ui_action_feedback\",\n                        \"comment_feedback\", \"success\", 0L);\n                loadData();\n                break;\n            default:\n                Tracker.getInstance().sendEvent(\"ui_action_feedback\",\n                        \"comment_feedback\", status.message, 0L);\n                CDToast.showToast(getActivity(), R.string.tip_comment_failure);\n                break;\n        }\n    }\n\n    @Override\n    public void onCommentFailure(Throwable e) {\n        CDToast.showToast(getActivity(), R.string.tip_comment_failure);\n        Tracker.getInstance().sendException(\"comment error!\", e, false);\n    }\n\n    @Override\n    public void onSessionExpired() {\n        Intent intent = new Intent(getActivity(), LoginActivity.class);\n        startActivity(intent);\n    }\n\n    public interface OnMenuSelectedListener {\n        public void onShowArticleSelected(SNNew snNew);\n\n        public void onUpVoteSelected(String postId);\n    }\n\n    private SNDiscuss mSnDiscuss;\n\n    private ListView mListView;\n\n    private String mDiscussURL;\n\n    private DiscussCommentAdapter mAdapter;\n\n    private TextView mTitle;\n\n    private TextView mSubTitle;\n\n    private TextView mText;\n\n    private Menu mOptionsMenu;\n\n    private EditText mCommentEdit;\n\n    private ImageButton mSendBtn;\n\n    private OnMenuSelectedListener mListener;\n\n    @Inject\n    SessionManager mSessionManager;\n\n    public DiscussFragment() {\n\n    }\n\n    public static DiscussFragment newInstance(String discussURL, SNNew snNew) {\n        DiscussFragment fragment = new DiscussFragment();\n        Bundle args = new Bundle();\n        args.putString(ARG_DISCUSS_URL, discussURL);\n        args.putParcelable(ARG_SNNEW, snNew);\n        fragment.setArguments(args);\n        return fragment;\n    }\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        if (activity instanceof OnMenuSelectedListener) {\n            mListener = (OnMenuSelectedListener) activity;\n        }\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n        setHasOptionsMenu(true);\n        View view = inflater.inflate(R.layout.fragment_discuss, null);\n        mListView = (ListView) view.findViewById(android.R.id.list);\n        mListView.setOnItemClickListener(this);\n        mSnDiscuss = new SNDiscuss();\n        Bundle args = getArguments();\n        SNNew snNew = null;\n        if (args != null && args.containsKey(ARG_DISCUSS_URL)) {\n            mDiscussURL = args.getString(ARG_DISCUSS_URL);\n        } else {\n            throw new IllegalArgumentException(\"Discuss URL is required!\");\n        }\n        if (args.containsKey(ARG_SNNEW)) {\n            snNew = args.getParcelable(ARG_SNNEW);\n            mSnDiscuss.setSnNew(snNew);\n        }\n        mAdapter = new DiscussCommentAdapter();\n        View headerView = inflater.inflate(R.layout.discuss_header_view, null);\n        mTitle = (TextView) headerView.findViewById(R.id.discuss_news_title);\n        mSubTitle = (TextView) headerView.findViewById(R.id.discuss_news_subtitle);\n        mText = (TextView) headerView.findViewById(R.id.discuss_text);\n\n        mSendBtn = (ImageButton) view.findViewById(R.id.discuss_comment_send_btn);\n        mSendBtn.setEnabled(false);\n        mSendBtn.setOnClickListener(mSendBtnClickListener);\n        mCommentEdit = (EditText) view.findViewById(R.id.discuss_comment_edit);\n        mCommentEdit.addTextChangedListener(new TextWatcher() {\n\n            @Override\n            public void onTextChanged(CharSequence s, int start, int before, int count) {\n\n            }\n\n            @Override\n            public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n\n            }\n\n            @Override\n            public void afterTextChanged(Editable s) {\n                mSendBtn.setEnabled(s.length() > 0);\n            }\n        });\n        mListView.addHeaderView(headerView);\n        mListView.setAdapter(mAdapter);\n        wrapHeaderView(snNew);\n        loadData();\n        return view;\n    }\n\n    private OnClickListener mSendBtnClickListener = new OnClickListener() {\n\n        @Override\n        public void onClick(View v) {\n            Tracker.getInstance().sendEvent(\"ui_action\", \"view_clicked\",\n                    \"discussactivity_button_comment\", 0L);\n            mPresenter.comment(mCommentEdit.getText().toString());\n        }\n    };\n\n    @Override\n    public void onDestroy() {\n        super.onDestroy();\n    }\n\n    @Override\n    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n        super.onCreateOptionsMenu(menu, inflater);\n        mOptionsMenu = menu;\n        inflater.inflate(R.menu.fragment_discuss, menu);\n    }\n\n    private void wrapHeaderView(SNNew snNew) {\n        if (snNew != null) {\n            mTitle.setText(snNew.getTitle());\n            mSubTitle.setText(Html.fromHtml(snNew.getSubText()));\n            if (snNew.isDiscuss()) {\n                mText.setVisibility(View.VISIBLE);\n                mText.setText(snNew.getText());\n            } else {\n                mText.setVisibility(View.GONE);\n            }\n        }\n    }\n\n    public void setRefreshActionButtonState(boolean refreshing) {\n        if (mOptionsMenu == null) {\n            Log.i(LOG_TAG, \"Option menu is null!\");\n            return;\n        }\n\n        final MenuItem refreshItem = mOptionsMenu.findItem(R.id.menu_refresh);\n        if (refreshItem != null) {\n            if (refreshing) {\n                refreshItem.setActionView(R.layout.actionbar_indeterminate_progress);\n            } else {\n                refreshItem.setActionView(null);\n            }\n        }\n    }\n\n    public void loadData() {\n        mPresenter.getDiscuss(mDiscussURL);\n        setRefreshActionButtonState(true);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case R.id.menu_refresh:\n                Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\", \"discussactivity_menu_refresh\", 0L);\n                loadData();\n                return true;\n            case R.id.menu_show_article:\n                if (mListener != null) {\n                    mListener.onShowArticleSelected(mSnDiscuss.getSnNew());\n                }\n                return true;\n            case R.id.menu_up_vote:\n                if (mListener != null) {\n                    mListener.onUpVoteSelected(mSnDiscuss.getSnNew().getPostID());\n                }\n            default:\n                return super.onOptionsItemSelected(item);\n        }\n\n    }\n\n    private class DiscussCommentAdapter extends BaseAdapter {\n\n        @Override\n        public int getCount() {\n            return mSnDiscuss.commentSize();\n        }\n\n        @Override\n        public Object getItem(int position) {\n            return mSnDiscuss.getComments().get(position);\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            ViewHolder holder;\n            if (convertView == null) {\n                holder = new ViewHolder();\n                convertView = LayoutInflater.from(getActivity()).inflate(\n                        R.layout.discuss_comment_item, null);\n                holder.mUserId = (TextView) convertView\n                        .findViewById(R.id.discuss_comment_item_user_id);\n                holder.mCreated = (TextView) convertView\n                        .findViewById(R.id.discuss_comment_item_created);\n                holder.mCommentText = (TextView) convertView\n                        .findViewById(R.id.discuss_comment_item_text);\n                holder.mArtistTitle = (TextView) convertView\n                        .findViewById(R.id.discuss_comment_item_artist_titile);\n                convertView.setTag(holder);\n            } else {\n                holder = (ViewHolder) convertView.getTag();\n            }\n            SNComment comment = mSnDiscuss.getComments().get(position);\n            holder.mUserId.setText(comment.getUser().getId());\n            holder.mCreated.setText(comment.getCreated());\n            holder.mCommentText.setText(comment.getText());\n            holder.mArtistTitle.setVisibility(View.GONE);\n            return convertView;\n        }\n\n        class ViewHolder {\n            TextView mUserId;\n\n            TextView mCreated;\n\n            TextView mCommentText;\n\n            TextView mArtistTitle;\n        }\n\n    }\n\n    @Override\n    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n        if (position == 0 && mListener == null) {\n            Tracker.getInstance().sendEvent(\"ui_action\", \"list_item_click\",\n                    \"discuss_activity_list_header_click\", 0L);\n            // 查看文章\n            ActivityUtils.openArticle(getActivity(), mSnDiscuss.getSnNew());\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/CardViewDividerDecoration.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\nimport com.halzhang.android.apps.startupnews.R;\n\n/**\n * Divider for {@link RecyclerView}\n * Created by Hal on 15/7/19.\n */\npublic class CardViewDividerDecoration extends RecyclerView.ItemDecoration {\n\n    private Drawable mDivider;\n    private int mInsets;\n\n    public CardViewDividerDecoration(Context context) {\n        mDivider = new ColorDrawable(context.getResources().getColor(android.R.color.transparent));\n        mInsets = context.getResources().getDimensionPixelSize(R.dimen.card_insets);\n    }\n\n    @Override\n    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {\n        drawVertical(c, parent);\n    }\n\n    /**\n     * Draw dividers underneath each child view\n     */\n    public void drawVertical(Canvas c, RecyclerView parent) {\n        final int left = parent.getPaddingLeft();\n        final int right = parent.getWidth() - parent.getPaddingRight();\n\n        final int childCount = parent.getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            final View child = parent.getChildAt(i);\n            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child\n                    .getLayoutParams();\n            final int top = child.getBottom() + params.bottomMargin + mInsets;\n            final int bottom = top + mDivider.getIntrinsicHeight();\n            mDivider.setBounds(left, top, right, bottom);\n            mDivider.draw(c);\n        }\n    }\n\n    @Override\n    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {\n        //We can supply forced insets for each item view here in the Rect\n        outRect.set(mInsets, mInsets, mInsets, mInsets);\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/DividerDecoration.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.drawable.Drawable;\nimport android.support.v7.widget.RecyclerView;\nimport android.view.View;\n\nimport com.halzhang.android.apps.startupnews.R;\n\n/**\n * Divider for {@link RecyclerView}\n * Created by Hal on 15/7/19.\n */\npublic class DividerDecoration extends RecyclerView.ItemDecoration {\n\n    private static final int[] ATTRS = {android.R.attr.listDivider};\n\n    private Drawable mDivider;\n    private int mInsets;\n\n    public DividerDecoration(Context context) {\n        TypedArray a = context.obtainStyledAttributes(ATTRS);\n        mDivider = a.getDrawable(0);\n        a.recycle();\n\n        mInsets = context.getResources().getDimensionPixelSize(R.dimen.card_insets);\n    }\n\n    @Override\n    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {\n        drawVertical(c, parent);\n    }\n\n    /**\n     * Draw dividers underneath each child view\n     */\n    public void drawVertical(Canvas c, RecyclerView parent) {\n        final int left = parent.getPaddingLeft();\n        final int right = parent.getWidth() - parent.getPaddingRight();\n\n        final int childCount = parent.getChildCount();\n        for (int i = 0; i < childCount; i++) {\n            final View child = parent.getChildAt(i);\n            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child\n                    .getLayoutParams();\n            final int top = child.getBottom() + params.bottomMargin + mInsets;\n            final int bottom = top + mDivider.getIntrinsicHeight();\n            mDivider.setBounds(left, top, right, bottom);\n            mDivider.draw(c);\n        }\n    }\n\n    @Override\n    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {\n        //We can supply forced insets for each item view here in the Rect\n        outRect.set(mInsets, mInsets, mInsets, mInsets);\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/ObservableWebView.java",
    "content": "/*\n *\n * Copyright (C) 2013 HalZhang.\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.webkit.WebView;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Aug 25, 2013\n */\npublic class ObservableWebView extends WebView {\n\n    private final static int TOUCH_STATE_REST = 0;\n\n    private final static int TOUCH_STATE_SCROLL_UP = 1;\n\n    private final static int TOUCH_STATE_SCROLL_DOWN = 2;\n\n    private int mTouchState = TOUCH_STATE_REST;\n\n    private OnScrollChangedCallback mOnScrollChangedCallback;\n\n    public ObservableWebView(final Context context) {\n        super(context);\n    }\n\n    public ObservableWebView(final Context context, final AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public ObservableWebView(final Context context, final AttributeSet attrs, final int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {\n        super.onScrollChanged(l, t, oldl, oldt);\n        if (mOnScrollChangedCallback != null) {\n            mOnScrollChangedCallback.onScroll(l, t, oldl, oldt);\n            if (t >= 0 && oldt >= 0) {\n                if (oldt > t && mTouchState != TOUCH_STATE_SCROLL_UP) {\n                    mTouchState = TOUCH_STATE_SCROLL_UP;\n                    mOnScrollChangedCallback.onScrollUp();\n                } else if (oldt < t && mTouchState != TOUCH_STATE_SCROLL_DOWN) {\n                    mTouchState = TOUCH_STATE_SCROLL_DOWN;\n                    mOnScrollChangedCallback.onScrollDown();\n                }\n            }\n        }\n\n    }\n\n    public OnScrollChangedCallback getOnScrollChangedCallback() {\n        return mOnScrollChangedCallback;\n    }\n\n    public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback) {\n        mOnScrollChangedCallback = onScrollChangedCallback;\n    }\n\n    /**\n     * Impliment in the activity/fragment/view that you want to listen to the\n     * webview\n     */\n    public static interface OnScrollChangedCallback {\n        public void onScroll(int l, int t, int oldl, int oldt);\n\n        /**\n         * 向上滚动\n         */\n        public void onScrollUp();\n\n        /**\n         * 向下滚动\n         */\n        public void onScrollDown();\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/SwitchPreference.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport android.content.Context;\nimport android.preference.TwoStatePreference;\nimport android.util.AttributeSet;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 17, 2013\n */\npublic class SwitchPreference extends TwoStatePreference {\n\n    public SwitchPreference(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/WebViewController.java",
    "content": "package com.halzhang.android.apps.startupnews.ui.widgets;\n\nimport com.halzhang.android.apps.startupnews.R;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;\nimport com.halzhang.android.apps.startupnews.utils.UIUtils;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.View.OnClickListener;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport android.webkit.WebChromeClient;\nimport android.webkit.WebSettings;\nimport android.webkit.WebView;\nimport android.webkit.WebViewClient;\nimport android.widget.ImageButton;\n\nimport java.lang.ref.WeakReference;\n\n/**\n * WebView and Browse Bar Controller\n *\n * @author Hal\n */\npublic class WebViewController implements OnClickListener {\n\n    private WeakReference<Activity> mActivityRef;\n\n    private WebView mWebView;\n\n    private ImageButton mBackButton;\n    private ImageButton mForwardButton;\n    private ImageButton mReadabilityButton;\n    private ImageButton mRefreshButton;\n    private ImageButton mWebSiteButton;\n\n    private View mToolBar;\n\n    private String mCurrentUrl;\n\n    public WebViewController(Activity activity) {\n        mActivityRef = new WeakReference<Activity>(activity);\n    }\n\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    public void initControllerView(WebView webView, View view) {\n        if (webView == null || view == null) {\n            return;\n        }\n        mWebView = webView;\n        mWebView.setWebChromeClient(new MyWebChromeClient());\n        mWebView.setWebViewClient(new MyWebViewClient());\n\n        WebSettings settings = mWebView.getSettings();\n        settings.setSupportZoom(true);\n        settings.setBuiltInZoomControls(true);\n        if (UIUtils.hasHoneycomb()) {\n            settings.setDisplayZoomControls(false);\n        }\n        settings.setJavaScriptEnabled(true);\n        settings.setUseWideViewPort(true);\n        settings.setLoadWithOverviewMode(true);\n        mBackButton = (ImageButton) view.findViewById(R.id.browse_back);\n        mForwardButton = (ImageButton) view.findViewById(R.id.browse_forward);\n        mReadabilityButton = (ImageButton) view.findViewById(R.id.browse_readability);\n        mRefreshButton = (ImageButton) view.findViewById(R.id.browse_refresh);\n        mWebSiteButton = (ImageButton) view.findViewById(R.id.browse_website);\n\n        mToolBar = view.findViewById(R.id.browse_bar);\n\n        mBackButton.setOnClickListener(this);\n        mForwardButton.setOnClickListener(this);\n        mReadabilityButton.setOnClickListener(this);\n        mRefreshButton.setOnClickListener(this);\n        mWebSiteButton.setOnClickListener(this);\n    }\n\n    private class MyWebViewClient extends WebViewClient {\n        @Override\n        public boolean shouldOverrideUrlLoading(WebView view, String url) {\n            view.loadUrl(url);\n            return true;\n        }\n\n        @Override\n        public void onPageStarted(WebView view, String url, Bitmap favicon) {\n            super.onPageStarted(view, url, favicon);\n            setCurrentUrl(url);\n        }\n\n        @Override\n        public void onPageFinished(WebView view, String url) {\n            super.onPageFinished(view, url);\n            setCurrentUrl(url);\n        }\n    }\n\n    private void setCurrentUrl(String url) {\n        mCurrentUrl = url;\n    }\n\n    private String getCurrentUrl() {\n        return mCurrentUrl; //TextUtils.isEmpty(mCurrentUrl) ? mOriginalUrl : mCurrentUrl;\n    }\n\n    private class MyWebChromeClient extends WebChromeClient {\n\n        @Override\n        public void onProgressChanged(WebView view, int newProgress) {\n            super.onProgressChanged(view, newProgress);\n            int progress = (Window.PROGRESS_END - Window.PROGRESS_START) / 100 * newProgress;\n            Activity activity = mActivityRef.get();\n            if (activity != null && activity instanceof BaseFragmentActivity) {\n                ((BaseFragmentActivity) activity).setSupportProgress(progress);\n            }\n        }\n\n        @Override\n        public void onReceivedTitle(WebView view, String title) {\n            super.onReceivedTitle(view, title);\n            mActivityRef.get().setTitle(title);\n        }\n\n    }\n\n    @Override\n    public void onClick(View v) {\n        switch (v.getId()) {\n            case R.id.browse_back:\n                back();\n                break;\n            case R.id.browse_forward:\n                forward();\n                break;\n            case R.id.browse_readability:\n                readability();\n                break;\n            case R.id.browse_refresh:\n                refresh();\n                break;\n            case R.id.browse_website:\n                webSite();\n                break;\n            default:\n                break;\n        }\n\n    }\n\n    private void back() {\n        Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                \"browseactivity_menu_back\", 0L);\n        if (mWebView.canGoBack()) {\n            mWebView.goBack();\n        }\n    }\n\n    private void forward() {\n        Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                \"browseactivity_menu_forward\", 0L);\n        if (mWebView.canGoForward()) {\n            mWebView.goForward();\n        }\n    }\n\n    private void readability() {\n        Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                \"browseactivity_menu_readability\", 0L);\n        if (TextUtils.isEmpty(mCurrentUrl)) {\n            return;\n        }\n        mWebView.loadUrl(\"http://www.readability.com/m?url=\" + getCurrentUrl());\n    }\n\n    private void refresh() {\n        Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                \"browseactivity_menu_refresh\", 0L);\n        mWebView.reload();\n    }\n\n    private void webSite() {\n        // 打开原链接，还是转码的链接呢？\n        Tracker.getInstance().sendEvent(\"ui_action\", \"options_item_selected\",\n                \"browseactivity_menu_website\", 0L);\n        if (TextUtils.isEmpty(mCurrentUrl)) {\n            return;\n        }\n        Intent intent = new Intent(Intent.ACTION_VIEW);\n        intent.setData(Uri.parse(getCurrentUrl()));\n        mActivityRef.get().startActivity(intent);\n    }\n\n    public WebView getWebView() {\n        return mWebView;\n    }\n\n    public void destroy() {\n        if (mWebView != null) {\n            ((ViewGroup) mWebView.getParent()).removeAllViews();\n            mWebView.clearHistory();\n            mWebView.clearCache(true);\n            mWebView.loadUrl(\"about:blank\");\n            mWebView.pauseTimers();\n            mWebView.destroy();\n            mWebView = null;\n        }\n        mActivityRef.clear();\n    }\n\n    public void loadUrl(String url) {\n        if (TextUtils.isEmpty(url) || url.equals(mCurrentUrl)) {\n            return;\n        }\n        mWebView.clearHistory();\n        mWebView.loadUrl(url);\n    }\n\n    public void showBrowseBar() {\n        if (mToolBar != null) {\n            Animation animation = AnimationUtils.loadAnimation(mActivityRef.get(), R.anim.push_up_in);\n            mToolBar.startAnimation(animation);\n            mToolBar.setVisibility(View.VISIBLE);\n        }\n    }\n\n    public void hideBrowseBar() {\n        if (mToolBar != null) {\n            Animation animation = AnimationUtils.loadAnimation(mActivityRef.get(), R.anim.push_down_out);\n            mToolBar.startAnimation(animation);\n            mToolBar.setVisibility(View.GONE);\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityScoped.java",
    "content": "package com.halzhang.android.apps.startupnews.utils;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\nimport javax.inject.Scope;\n\n/**\n * Created by Hal on 16/6/12.\n */\n@Documented\n@Scope\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface ActivityScoped {\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/ActivityUtils.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport android.app.Activity;\nimport android.app.ActivityOptions;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.view.View;\n\nimport com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\n\nimport java.util.List;\n\n/**\n * StartupNews\n * <p>\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 22, 2013\n */\npublic class ActivityUtils {\n\n    public static void openArticle(Activity activity, SNNew snNew) {\n        if (snNew == null || activity == null) {\n            return;\n        }\n        Intent intent = null;\n        if (PreferenceUtils.isUseInnerBrowse(activity)) {\n            intent = new Intent(activity, BrowseActivity.class);\n            intent.putExtra(BrowseActivity.EXTRA_URL, snNew.getUrl());\n            intent.putExtra(BrowseActivity.EXTRA_TITLE, snNew.getTitle());\n        } else {\n            intent = new Intent(Intent.ACTION_VIEW);\n            intent.setData(Uri.parse(PreferenceUtils.getHtmlProvider(activity) + snNew.getUrl()));\n        }\n        activity.startActivity(intent);\n        AppUtils.getMyApplication(activity).addHistory(snNew.getUrl());\n    }\n\n    /**\n     * With window Animations\n     *\n     * @param activity\n     * @param snNew\n     * @param v\n     */\n    public static void openActicle(Activity activity, SNNew snNew, View v) {\n        if (snNew == null || activity == null) {\n            return;\n        }\n        Bundle b = null;\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            // b = ActivityOptions.makeScaleUpAnimation(view, 0, 0,\n            // view.getWidth(),\n            // view.getHeight()).toBundle();\n            Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(),\n                    Bitmap.Config.ARGB_8888);\n            bitmap.eraseColor(Color.WHITE);\n            b = ActivityOptions.makeThumbnailScaleUpAnimation(v, bitmap, 0, 0).toBundle();\n        }\n        Intent intent = null;\n        if (PreferenceUtils.isUseInnerBrowse(activity)) {\n            intent = new Intent(activity, BrowseActivity.class);\n            intent.putExtra(BrowseActivity.EXTRA_URL, snNew.getUrl());\n            intent.putExtra(BrowseActivity.EXTRA_TITLE, snNew.getTitle());\n        } else {\n            intent = new Intent(Intent.ACTION_VIEW);\n            intent.setData(Uri.parse(PreferenceUtils.getHtmlProvider(activity) + snNew.getUrl()));\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n            activity.startActivity(intent, b);\n        } else {\n            activity.startActivity(intent);\n        }\n    }\n\n    /**\n     * 判断Intent是否可用\n     *\n     * @param context\n     * @param intent\n     * @return\n     */\n    public static boolean isIntentAvailable(Context context, Intent intent) {\n        final PackageManager packageManager = context.getPackageManager();\n        List<ResolveInfo> list = packageManager.queryIntentActivities(intent,\n                PackageManager.GET_ACTIVITIES);\n        return list.size() > 0;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/AppUtils.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport com.halzhang.android.apps.startupnews.MyApplication;\n\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager.NameNotFoundException;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 12, 2013\n */\npublic class AppUtils {\n\n    public static String getVersionName(Context context) {\n        try {\n\n            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),\n                    0);\n            return info.versionName;\n        } catch (NameNotFoundException e) {\n            return \"\";\n        }\n    }\n\n    public static int getVersionCode(Context context) {\n        try {\n\n            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),\n                    0);\n            return info.versionCode;\n        } catch (NameNotFoundException e) {\n            return 0;\n        }\n    }\n\n    public static MyApplication getMyApplication(Context context) {\n        return (MyApplication) context.getApplicationContext();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/CrashHandler.java",
    "content": "\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport com.google.analytics.tracking.android.EasyTracker;\nimport com.halzhang.android.apps.startupnews.BuildConfig;\nimport com.halzhang.android.apps.startupnews.Constants;\nimport com.halzhang.android.apps.startupnews.MyApplication;\nimport com.halzhang.android.apps.startupnews.analytics.Tracker;\nimport com.halzhang.android.common.CDLog;\n\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.PackageManager.NameNotFoundException;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.lang.Thread.UncaughtExceptionHandler;\nimport java.lang.reflect.Field;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\n\n/**\n * Crash info collection\n */\npublic class CrashHandler implements UncaughtExceptionHandler {\n\n    private static final String LOG_TAG = CrashHandler.class.getSimpleName();\n\n    private static CrashHandler me = null;\n\n    private Context mContext;\n\n    private Map<String, String> infos = new HashMap<String, String>();\n\n    private DateFormat formatter = new SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\", Locale.getDefault());\n\n    private CrashHandler() {\n    }\n\n    public static CrashHandler getInstance() {\n        if (me == null) {\n            me = new CrashHandler();\n        }\n        return me;\n    }\n\n    public void init(Context context) {\n        mContext = context;\n        Thread.setDefaultUncaughtExceptionHandler(this);\n    }\n\n    @Override\n    public void uncaughtException(Thread thread, Throwable ex) {\n        if (BuildConfig.DEBUG) {\n            Log.e(LOG_TAG, ex.getMessage(), ex);\n            handleException(ex);\n        }\n        Tracker.getInstance().sendException(ex.getMessage(), ex, true);\n        android.os.Process.killProcess(android.os.Process.myPid());\n        System.exit(0);\n    }\n\n    private boolean handleException(Throwable ex) {\n        if (ex != null) {\n            collectDeviceInfo(mContext);\n            saveCrashInfo2File(ex);\n            return true;\n        }\n        return false;\n    }\n\n    private void collectDeviceInfo(Context ctx) {\n        try {\n            PackageManager pm = ctx.getPackageManager();\n            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);\n            if (pi != null) {\n                String versionName = pi.versionName == null ? \"null\" : pi.versionName;\n                String versionCode = pi.versionCode + \"\";\n                infos.put(\"versionName\", versionName);\n                infos.put(\"versionCode\", versionCode);\n            }\n        } catch (NameNotFoundException e) {\n        }\n        Field[] fields = Build.class.getDeclaredFields();\n        for (Field field : fields) {\n            try {\n                field.setAccessible(true);\n                infos.put(field.getName(), field.get(null).toString());\n                //Log.d(LOG_TAG, field.getName() + \" : \" + field.get(null));\n            } catch (Exception e) {\n            }\n        }\n    }\n\n    /**\n     */\n    private String saveCrashInfo2File(Throwable ex) {\n\n        StringBuffer sb = new StringBuffer();\n        for (Map.Entry<String, String> entry : infos.entrySet()) {\n            String key = entry.getKey();\n            String value = entry.getValue();\n            sb.append(key + \"=\" + value + \"\\n\");\n        }\n\n        Writer writer = new StringWriter();\n        PrintWriter printWriter = new PrintWriter(writer);\n        ex.printStackTrace(printWriter);\n        Throwable cause = ex.getCause();\n        while (cause != null) {\n            cause.printStackTrace(printWriter);\n            cause = cause.getCause();\n        }\n        printWriter.close();\n        String result = writer.toString();\n        sb.append(result);\n        try {\n            long timestamp = System.currentTimeMillis();\n            String time = formatter.format(new Date());\n            String fileName = \"crash-\" + time + \"-\" + timestamp + \".txt\";\n            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {\n                String path = Environment.getExternalStorageDirectory().getAbsolutePath()\n                        + Constants.CRASH_LOG_DIR;\n                File dir = new File(path);\n                if (!dir.exists()) {\n                    dir.mkdirs();\n                }\n                FileOutputStream fos = new FileOutputStream(path + fileName);\n                fos.write(sb.toString().getBytes());\n                fos.close();\n            }\n            return fileName;\n        } catch (Exception e) {\n            Log.e(LOG_TAG, \"an error occured while writing file...\", e);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsActivityHelper.java",
    "content": "package com.halzhang.android.apps.startupnews.utils;\n\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.support.customtabs.CustomTabsCallback;\nimport android.support.customtabs.CustomTabsClient;\nimport android.support.customtabs.CustomTabsIntent;\nimport android.support.customtabs.CustomTabsServiceConnection;\nimport android.support.customtabs.CustomTabsSession;\nimport android.text.TextUtils;\nimport android.util.Log;\n\n/**\n * 界面 helper\n * Created by Hal on 15/11/25.\n */\npublic class CustomTabsActivityHelper {\n\n    private static final String TAG = CustomTabsActivityHelper.class.getSimpleName();\n\n\n    private CustomTabsClient mClient;\n    private CustomTabsSession mSession;\n    private CustomTabsServiceConnection mConnection;\n\n    public void bindService(Context context) {\n        if (mConnection != null) {\n            return;\n        }\n        String packageName = CustomTabsHelper.getPackageNameToUse(context);\n        Log.i(TAG, \"Package name: \" + packageName);\n        if (TextUtils.isEmpty(packageName)) {\n            return;\n        }\n        mConnection = new CustomTabsServiceConnection() {\n            @Override\n            public void onCustomTabsServiceConnected(ComponentName componentName, CustomTabsClient customTabsClient) {\n                Log.i(TAG, \"Custom tabs service connected!\");\n                mClient = customTabsClient;\n                mClient.warmup(0);\n                mSession = mClient.newSession(new CustomTabsCallback());\n            }\n\n            @Override\n            public void onServiceDisconnected(ComponentName name) {\n                mClient = null;\n                mSession = null;\n            }\n        };\n        CustomTabsClient.bindCustomTabsService(context, packageName, mConnection);\n    }\n\n    public CustomTabsSession getSession() {\n        if (mClient == null) {\n            return null;\n        }\n        if (mSession == null) {\n            mSession = mClient.newSession(new CustomTabsCallback());\n        }\n        return mSession;\n    }\n\n    public void unBindService(Context context) {\n        if (mConnection == null) {\n            return;\n        }\n        context.unbindService(mConnection);\n        mConnection = null;\n    }\n\n    /**\n     * 启动\n     *\n     * @param activity         {@link Activity}\n     * @param url              链接\n     * @param customTabsIntent {@link CustomTabsIntent},由用户自定义属性\n     * @param listener         {@link com.halzhang.android.apps.startupnews.utils.CustomTabsActivityHelper.OnCustomTabsInvalidListener},没有处理，使用默认浏览器打开\n     */\n    public void launchUrl(Activity activity, String url, CustomTabsIntent customTabsIntent, OnCustomTabsInvalidListener listener) {\n        String packageName = CustomTabsHelper.getPackageNameToUse(activity);\n        if (TextUtils.isEmpty(packageName)) {\n            if (listener != null) {\n                listener.onInvalid(url);\n            } else {\n                Intent intent = new Intent(Intent.ACTION_VIEW);\n                intent.setData(Uri.parse(url));\n                activity.startActivity(intent);\n            }\n        } else {\n            customTabsIntent.launchUrl(activity, Uri.parse(url));\n        }\n    }\n\n    /**\n     * 无效监听\n     */\n    public interface OnCustomTabsInvalidListener {\n        /**\n         * 当 custom tabs 无效时处理\n         *\n         * @param url 链接\n         */\n        public void onInvalid(String url);\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/CustomTabsHelper.java",
    "content": "package com.halzhang.android.apps.startupnews.utils;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.net.Uri;\nimport android.support.customtabs.CustomTabsService;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * helper\n * Created by Hal on 15/11/25.\n */\npublic class CustomTabsHelper {\n\n    private static final String TAG = \"CustomTabsHelper\";\n    static final String STABLE_PACKAGE = \"com.android.chrome\";\n    static final String BETA_PACKAGE = \"com.chrome.beta\";\n    static final String DEV_PACKAGE = \"com.chrome.dev\";\n    static final String LOCAL_PACKAGE = \"com.google.android.apps.chrome\";\n    private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =\n            \"android.support.customtabs.extra.KEEP_ALIVE\";\n\n    private static String sPackageNameToUse;\n\n    private CustomTabsHelper() {\n    }\n\n    /**\n     * Goes through all apps that handle VIEW intents and have a warmup service. Picks\n     * the one chosen by the user if there is one, otherwise makes a best effort to return a\n     * valid package name.\n     * <p/>\n     * This is <strong>not</strong> threadsafe.\n     *\n     * @param context {@link Context} to use for accessing {@link PackageManager}.\n     * @return The package name recommended to use for connecting to custom tabs related components.\n     */\n    public static String getPackageNameToUse(Context context) {\n        if (sPackageNameToUse != null) return sPackageNameToUse;\n\n        PackageManager pm = context.getPackageManager();\n        // Get default VIEW intent handler.\n        Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(\"http://www.example.com\"));\n        ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);\n        String defaultViewHandlerPackageName = null;\n        if (defaultViewHandlerInfo != null) {\n            defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;\n        }\n\n        // Get all apps that can handle VIEW intents.\n        List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);\n        List<String> packagesSupportingCustomTabs = new ArrayList<>();\n        for (ResolveInfo info : resolvedActivityList) {\n            Intent serviceIntent = new Intent();\n            serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);\n            serviceIntent.setPackage(info.activityInfo.packageName);\n            if (pm.resolveService(serviceIntent, 0) != null) {\n                packagesSupportingCustomTabs.add(info.activityInfo.packageName);\n            }\n        }\n\n        // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents\n        // and service calls.\n        if (packagesSupportingCustomTabs.isEmpty()) {\n            sPackageNameToUse = null;\n        } else if (packagesSupportingCustomTabs.size() == 1) {\n            sPackageNameToUse = packagesSupportingCustomTabs.get(0);\n        } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)\n                && !hasSpecializedHandlerIntents(context, activityIntent)\n                && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {\n            sPackageNameToUse = defaultViewHandlerPackageName;\n        } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {\n            sPackageNameToUse = STABLE_PACKAGE;\n        } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {\n            sPackageNameToUse = BETA_PACKAGE;\n        } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {\n            sPackageNameToUse = DEV_PACKAGE;\n        } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {\n            sPackageNameToUse = LOCAL_PACKAGE;\n        }\n        return sPackageNameToUse;\n    }\n\n    /**\n     * Used to check whether there is a specialized handler for a given intent.\n     *\n     * @param intent The intent to check with.\n     * @return Whether there is a specialized handler for the given intent.\n     */\n    private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {\n        try {\n            PackageManager pm = context.getPackageManager();\n            List<ResolveInfo> handlers = pm.queryIntentActivities(\n                    intent,\n                    PackageManager.GET_RESOLVED_FILTER);\n            if (handlers == null || handlers.size() == 0) {\n                return false;\n            }\n            for (ResolveInfo resolveInfo : handlers) {\n                IntentFilter filter = resolveInfo.filter;\n                if (filter == null) continue;\n                if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;\n                if (resolveInfo.activityInfo == null) continue;\n                return true;\n            }\n        } catch (RuntimeException e) {\n            Log.e(TAG, \"Runtime exception while getting specialized handlers\");\n        }\n        return false;\n    }\n\n    /**\n     * @return All possible chrome package names that provide custom tabs feature.\n     */\n    public static String[] getPackages() {\n        return new String[]{\"\", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/DateUtils.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport android.content.Context;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 12, 2013\n */\npublic class DateUtils {\n    \n    public static String getLastUpdateLabel(Context context){\n        final SimpleDateFormat format = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        String ds = format.format(new Date(System.currentTimeMillis()));\n        return \"上次更新:\"+ds;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/FragmentScoped.java",
    "content": "package com.halzhang.android.apps.startupnews.utils;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\nimport javax.inject.Scope;\n\n/**\n * Created by Hal on 16/6/12.\n */\n@Documented\n@Scope\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface FragmentScoped {\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/PreferenceUtils.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.apps.startupnews.utils;\n\nimport com.halzhang.android.apps.startupnews.R;\n\nimport android.content.Context;\nimport android.preference.PreferenceManager;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 17, 2013\n */\npublic class PreferenceUtils {\n\n    /**\n     * 获取网页阅读模式\n     * \n     * @param context\n     * @return\n     */\n    public static String getHtmlProvider(Context context) {\n        return PreferenceManager.getDefaultSharedPreferences(context).getString(\n                context.getString(R.string.pref_key_html_provider),\n                context.getString(R.string.default_html_provider));\n    }\n\n    /**\n     * 使用内置浏览器\n     * \n     * @param context\n     * @return\n     */\n    public static boolean isUseInnerBrowse(Context context) {\n        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(\n                context.getString(R.string.pref_key_default_browse), true);\n    }\n\n    public static void set(Context ctx, final String key, final String value) {\n        PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(key, value).commit();\n    }\n\n    public static String get(Context context, final String key) {\n        return PreferenceManager.getDefaultSharedPreferences(context).getString(key, null);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/halzhang/android/apps/startupnews/utils/UIUtils.java",
    "content": "package com.halzhang.android.apps.startupnews.utils;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.content.res.Configuration;\nimport android.os.Build;\nimport android.view.View;\n\n/**\n * UI工具\n * Created by Hal on 13-5-26.\n */\npublic final class UIUtils {\n\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    public static void setActivatedCompat(View view, boolean activated) {\n        if (hasHoneycomb()) {\n            view.setActivated(activated);\n        }\n    }\n\n    public static boolean isGoogleTV(Context context) {\n        return context.getPackageManager().hasSystemFeature(\"com.google.android.tv\");\n    }\n\n    public static boolean hasFroyo() {\n        // Can use static final constants like FROYO, declared in later versions\n        // of the OS since they are inlined at compile time. This is guaranteed behavior.\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;\n    }\n\n    public static boolean hasGingerbread() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;\n    }\n\n    public static boolean hasHoneycomb() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;\n    }\n\n    public static boolean hasHoneycombMR1() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;\n    }\n\n    public static boolean hasICS() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;\n    }\n\n    public static boolean hasJellyBean() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;\n    }\n\n    public static boolean isTablet(Context context) {\n        return (context.getResources().getConfiguration().screenLayout\n                & Configuration.SCREENLAYOUT_SIZE_MASK)\n                >= Configuration.SCREENLAYOUT_SIZE_LARGE;\n    }\n\n    public static boolean isHoneycombTablet(Context context) {\n        return hasHoneycomb() && isTablet(context);\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/anim/push_down_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n  \n          http://www.apache.org/licenses/LICENSE-2.0\n  \n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromYDelta=\"0\" android:toYDelta=\"100%p\"\n            android:duration=\"@android:integer/config_longAnimTime\"/>\n\t<alpha android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n            android:duration=\"@android:integer/config_longAnimTime\" />\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/push_up_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n  \n          http://www.apache.org/licenses/LICENSE-2.0\n  \n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromYDelta=\"100%p\" android:toYDelta=\"0\"\n            android:duration=\"@android:integer/config_longAnimTime\"/>\n\t<alpha android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n            android:duration=\"@android:integer/config_longAnimTime\" />\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/slide_in_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_in_left.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"-50%p\" android:toXDelta=\"0\"\n            android:duration=\"@android:integer/config_mediumAnimTime\"/>\n\t<alpha android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n            android:duration=\"@android:integer/config_mediumAnimTime\" />\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/slide_in_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_in_right.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"50%p\" android:toXDelta=\"0\"\n            android:duration=\"@android:integer/config_mediumAnimTime\"/>\n\t<alpha android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n            android:duration=\"@android:integer/config_mediumAnimTime\" />\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/slide_out_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_out_left.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"0\" android:toXDelta=\"-50%p\"\n            android:duration=\"@android:integer/config_mediumAnimTime\"/>\n\t<alpha android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n            android:duration=\"@android:integer/config_mediumAnimTime\" />\n</set>\n"
  },
  {
    "path": "app/src/main/res/anim/slide_out_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_out_right.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\"); \n** you may not use this file except in compliance with the License. \n** You may obtain a copy of the License at \n**\n**     http://www.apache.org/licenses/LICENSE-2.0 \n**\n** Unless required by applicable law or agreed to in writing, software \n** distributed under the License is distributed on an \"AS IS\" BASIS, \n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n** See the License for the specific language governing permissions and \n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"0\" android:toXDelta=\"50%p\"\n            android:duration=\"@android:integer/config_mediumAnimTime\"/>\n\t<alpha android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n            android:duration=\"@android:integer/config_mediumAnimTime\" />\n</set>\n"
  },
  {
    "path": "app/src/main/res/drawable/action_bar_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2010 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n        android:startColor=\"#ffd1d2d4\"\n        android:endColor=\"#ff85878a\"\n        android:angle=\"270\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/background_boardless.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <item android:state_pressed=\"true\" android:drawable=\"@color/holo_blue_dark\"/>\n\t<item android:drawable=\"@android:color/transparent\"/>\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/background_holo_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2012 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n            android:startColor=\"#ff000000\"\n            android:endColor=\"#ff272d33\"\n            android:angle=\"270\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/background_holo_light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2012 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n            android:startColor=\"#ffe8e8e8\"\n            android:endColor=\"#ffffffff\"\n            android:angle=\"270\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/bg_discuss_article.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\" >\n    \n     <gradient\n        android:startColor=\"#FFFDFAF0\"\n        android:endColor=\"#FFFDFAF0\"\n        android:angle=\"45\"/>\n    <padding android:left=\"7dp\"\n        android:top=\"7dp\"\n        android:right=\"7dp\"\n        android:bottom=\"7dp\" />\n    <corners android:radius=\"2dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/fragment_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ fragment_shadow.xml\n  ~ \n  ~ Copyright (C) 2013 6 Wunderkinder GmbH\n  ~\n  ~ Author: Halzhang - @halzhang\n  ~ Version: 1.0\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\" >\n\n    <gradient\n        android:endColor=\"#78000000\"\n        android:startColor=\"#22FFFFFF\" />\n\n    <!-- <solid android:color=\"#FF000000\" /> -->\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/send_button_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2011 Google Inc.\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/ic_send_disabled_holo_light\" android:state_enabled=\"false\"/>\n    <item android:drawable=\"@drawable/ic_send_holo_light\"/> <!-- default -->\n\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/sidebar_shadow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  ~ sidebar_shadow.xml\n  ~ \n  ~ Copyright (C) 2013 6 Wunderkinder GmbH\n  ~\n  ~ Author: Jose L Ugia - @Jl_Ugia\n  ~ Designer: Benedikt Lehnert - @blehnert\n  ~ Designer: Timothy Achumba - @iam_timm\n  ~ Version: 1.0\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~     http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n-->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\" >\n    \n    <solid android:color=\"#22000000\"/>\n\n</shape>"
  },
  {
    "path": "app/src/main/res/layout/actionbar_indeterminate_progress.xml",
    "content": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_height=\"wrap_content\"\n    android:layout_width=\"@dimen/action_button_min_width\"\n    android:minWidth=\"@dimen/action_button_min_width\">\n    <ProgressBar android:layout_width=\"32dp\"\n        android:layout_height=\"32dp\"\n        android:layout_gravity=\"center\"\n        style=\"?indeterminateProgressStyle\" />\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_browse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fragment_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"/>"
  },
  {
    "path": "app/src/main/res/layout/activity_discuss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fragment_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"/>"
  },
  {
    "path": "app/src/main/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fragment_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"/>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.design.widget.CoordinatorLayout android:id=\"@+id/main_content\"\n                                                 xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                                                 xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                                                 android:layout_width=\"match_parent\"\n                                                 android:layout_height=\"match_parent\">\n\n    <android.support.design.widget.AppBarLayout\n        android:id=\"@+id/appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\">\n\n        <android.support.v7.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"?attr/colorPrimary\"\n            app:layout_scrollFlags=\"scroll|enterAlways\"\n            app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\"/>\n\n        <android.support.design.widget.TabLayout\n            android:id=\"@+id/tabs\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:tabIndicatorColor=\"@android:color/white\"\n            />\n\n    </android.support.design.widget.AppBarLayout>\n\n    <android.support.v4.view.ViewPager\n        android:id=\"@+id/pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"/>\n\n</android.support.design.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/browse_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:id=\"@+id/browse_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@drawable/background_holo_dark\"\n        android:orientation=\"horizontal\" >\n\n        <ImageButton\n            android:id=\"@id/browse_back\"\n            style=\"@style/borderlessButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:src=\"@drawable/ic_action_back\" />\n\n        <ImageButton\n            android:id=\"@id/browse_forward\"\n            style=\"@style/borderlessButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:src=\"@drawable/ic_action_forward\" />\n\n        <ImageButton\n            android:id=\"@id/browse_readability\"\n            style=\"@style/borderlessButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:src=\"@drawable/ic_action_readability\" />\n\n        <ImageButton\n            android:id=\"@id/browse_refresh\"\n            style=\"@style/borderlessButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:src=\"@drawable/ic_action_refresh\" />\n\n        <ImageButton\n            android:id=\"@id/browse_website\"\n            style=\"@style/borderlessButtonStyle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:src=\"@drawable/ic_action_website\" />\n    </LinearLayout>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/comment_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.CardView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clickable=\"true\"\n    android:foreground=\"?android:attr/selectableItemBackground\"\n    app:cardCornerRadius=\"3dp\"\n    app:cardElevation=\"5dp\">\n\n    <RelativeLayout\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"?android:attr/listPreferredItemHeight\">\n\n        <TextView\n            android:id=\"@+id/comment_item_user_id\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentLeft=\"true\"\n            android:layout_marginLeft=\"5dip\"\n            android:layout_marginTop=\"5dip\"\n            android:text=\"Fenng\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"\n            android:textColor=\"@color/holo_blue_dark\"\n            android:textStyle=\"bold\"/>\n\n        <TextView\n            android:id=\"@+id/comment_item_created\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_marginRight=\"5dip\"\n            android:layout_marginTop=\"5dip\"\n            android:text=\"1 hour ago\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"/>\n\n        <TextView\n            android:id=\"@+id/comment_item_text\"\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/comment_item_user_id\"\n            android:layout_margin=\"5dip\"\n            android:text=\"的方式的放松放松东方时空恨死哈\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\"/>\n\n        <TextView\n            android:id=\"@+id/comment_item_artist_titile\"\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/comment_item_text\"\n            android:layout_marginBottom=\"5dip\"\n            android:layout_marginLeft=\"5dip\"\n            android:layout_marginRight=\"5dip\"\n            android:ellipsize=\"end\"\n            android:gravity=\"right\"\n            android:lines=\"1\"\n            android:text=\"startup news Android client!\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"/>\n\n    </RelativeLayout>\n</android.support.v7.widget.CardView>"
  },
  {
    "path": "app/src/main/res/layout/discuss_comment_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"?android:attr/listPreferredItemHeight\" >\n\n    <TextView\n        android:id=\"@+id/discuss_comment_item_user_id\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_marginLeft=\"5dip\"\n        android:layout_marginTop=\"5dip\"\n        android:text=\"Fenng\"\n        android:textAppearance=\"?android:attr/textAppearanceSmall\"\n        android:textColor=\"@color/holo_blue_dark\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/discuss_comment_item_created\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_marginRight=\"5dip\"\n        android:layout_marginTop=\"5dip\"\n        android:text=\"1 hour ago\"\n        android:textAppearance=\"?android:attr/textAppearanceSmall\" />\n\n    <TextView\n        android:id=\"@+id/discuss_comment_item_text\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/discuss_comment_item_user_id\"\n        android:layout_margin=\"5dip\"\n        android:text=\"的方式的放松放松东方时空恨死哈\"\n        android:textAppearance=\"?android:attr/textAppearanceMedium\" />\n\n    <TextView\n        android:id=\"@+id/discuss_comment_item_artist_titile\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@id/discuss_comment_item_text\"\n        android:layout_marginBottom=\"5dip\"\n        android:layout_marginLeft=\"5dip\"\n        android:layout_marginRight=\"5dip\"\n        android:lines=\"1\"\n        android:ellipsize=\"end\"\n        android:gravity=\"right\"\n        android:text=\"startup news Android client!\"\n        android:textAppearance=\"?android:attr/textAppearanceSmall\" />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/discuss_header_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"fill_parent\"\n    android:layout_height=\"wrap_content\"\n    android:minHeight=\"?android:attr/listPreferredItemHeight\"\n    android:orientation=\"vertical\" >\n\n    <TextView\n        android:id=\"@+id/discuss_news_title\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textAppearance=\"?android:attr/textAppearanceLarge\"\n        android:layout_margin=\"5dip\"\n        android:textStyle=\"bold\" />\n\n    <TextView\n        android:id=\"@+id/discuss_news_subtitle\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dip\"\n        android:layout_marginLeft=\"5dip\"\n        android:layout_marginRight=\"5dip\"\n        android:textColorLink=\"@color/holo_blue_dark\" />\n\n    <TextView\n        android:id=\"@+id/discuss_text\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"5dip\"\n        android:layout_marginLeft=\"5dip\"\n        android:textAppearance=\"?android:attr/textAppearanceMedium\"\n        android:layout_marginRight=\"5dip\"\n        android:visibility=\"gone\"\n        android:background=\"@drawable/bg_discuss_article\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_browse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" >\n\n    <com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <include\n        android:layout_alignParentBottom=\"true\"\n        layout=\"@layout/browse_bar\" />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_discuss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\" >\n\n    <ListView\n        android:id=\"@android:id/list\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        android:layout_weight=\"1\"\n        android:divider=\"@android:color/darker_gray\"\n        android:dividerHeight=\"1px\"\n        android:headerDividersEnabled=\"true\" />\n\n    <LinearLayout\n        android:id=\"@+id/bottom_panel\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\" >\n\n        <!-- Divider -->\n\n        <View\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"2dp\"\n            android:background=\"@color/holo_blue_dark\" />\n\n        <LinearLayout\n            android:id=\"@+id/edit_panel\"\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"#fff\"\n            android:gravity=\"bottom\"\n            android:orientation=\"horizontal\" >\n\n            <EditText\n                android:id=\"@+id/discuss_comment_edit\"\n                android:layout_width=\"fill_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"bottom\"\n                android:layout_weight=\"1\"\n                android:autoText=\"true\"\n                android:capitalize=\"sentences\"\n                android:hint=\"@string/hint_add_comment\"\n                android:imeOptions=\"actionSend|flagNoEnterAction\"\n                android:maxLines=\"3\"\n                android:textSize=\"16sp\" />\n\n            <ImageButton\n                android:id=\"@+id/discuss_comment_send_btn\"\n                style=\"?android:attr/borderlessButtonStyle\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:src=\"@drawable/send_button_selector\"/>\n            \n        </LinearLayout>\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <!-- Login progress -->\n\n    <LinearLayout\n        android:id=\"@+id/login_status\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\" >\n\n        <ProgressBar\n            style=\"?android:attr/progressBarStyleLarge\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"8dp\" />\n\n        <TextView\n            android:id=\"@+id/login_status_message\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"16dp\"\n            android:fontFamily=\"sans-serif-light\"\n            android:text=\"@string/login_progress_signing_in\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\" />\n    </LinearLayout>\n\n    <!-- Login form -->\n\n    <ScrollView\n        android:id=\"@+id/login_form\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\" >\n\n        <LinearLayout\n            style=\"@style/LoginFormContainer\"\n            android:orientation=\"vertical\" >\n\n            <EditText\n                android:id=\"@+id/username\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/prompt_username\"\n                android:maxLines=\"1\"\n                android:singleLine=\"true\"\n                android:imeOptions=\"flagNoFullscreen\" />\n\n            <EditText\n                android:id=\"@+id/password\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/prompt_password\"\n                android:imeActionId=\"@+id/login\"\n                android:imeActionLabel=\"@string/action_sign_in_short\"\n                android:imeOptions=\"actionUnspecified|flagNoFullscreen\"\n                android:inputType=\"textPassword\"\n                android:maxLines=\"1\"\n                android:singleLine=\"true\" />\n\n            <Button\n                android:id=\"@+id/btn_login\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/menu_login\"/>\n        </LinearLayout>\n    </ScrollView>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/news_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.CardView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clickable=\"true\"\n    android:foreground=\"?android:attr/selectableItemBackground\"\n    app:cardCornerRadius=\"3dp\"\n    app:cardElevation=\"5dp\">\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"?android:attr/listPreferredItemHeight\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/news_item_user\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"5dip\"\n            android:lines=\"1\"\n            android:text=\"halzhang\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"\n            android:textColor=\"@color/holo_blue_dark\"\n            android:textStyle=\"bold\"/>\n\n        <TextView\n            android:id=\"@+id/news_item_createat\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_margin=\"5dip\"\n            android:gravity=\"right\"\n            android:lines=\"1\"\n            android:text=\"1 hour age\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"/>\n\n        <TextView\n            android:id=\"@+id/news_item_title\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/news_item_user\"\n            android:layout_marginLeft=\"5dip\"\n            android:layout_marginRight=\"5dip\"\n            android:text=\"Androodban\"\n            android:textAppearance=\"?android:attr/textAppearanceMedium\"\n            />\n\n        <TextView\n            android:id=\"@+id/news_item_subtext\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/news_item_title\"\n            android:layout_margin=\"5dip\"\n            android:text=\"subtext\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"\n            android:textColorLink=\"@color/holo_blue_dark\"/>\n\n        <TextView\n            android:id=\"@+id/news_item_domain\"\n            android:layout_width=\"fill_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_below=\"@id/news_item_title\"\n            android:layout_margin=\"5dip\"\n            android:layout_toRightOf=\"@id/news_item_subtext\"\n            android:gravity=\"right\"\n            android:lines=\"1\"\n            android:text=\"halahznddddddddddddddddddddddddddddddagcom\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"/>\n\n    </RelativeLayout>\n</android.support.v7.widget.CardView>\n"
  },
  {
    "path": "app/src/main/res/layout/preference_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v7.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/toolbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/colorPrimary\"\n    android:minHeight=\"?attr/actionBarSize\"\n    app:navigationContentDescription=\"@string/abc_action_bar_up_description\"\n    app:navigationIcon=\"?attr/homeAsUpIndicator\"\n    app:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n    app:title=\"@string/title_settings\" />"
  },
  {
    "path": "app/src/main/res/layout/ptr_list_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\" >\n    <com.handmark.pulltorefresh.library.PullToRefreshListView\n        android:id=\"@id/pull_refresh_list\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        android:cacheColorHint=\"#00000000\"\n        android:divider=\"@android:color/darker_gray\"\n        android:dividerHeight=\"1px\"\n        android:fadingEdge=\"none\"\n        android:fastScrollEnabled=\"true\"\n        android:footerDividersEnabled=\"false\"\n        android:headerDividersEnabled=\"false\"\n        android:smoothScrollbar=\"true\"/>\n    <LinearLayout \n        android:id=\"@android:id/empty\"\n        android:layout_width=\"fill_parent\"\n        android:layout_height=\"fill_parent\"\n        android:orientation=\"vertical\"\n        android:gravity=\"center\">\n        <ProgressBar \n            style=\"?indeterminateProgressStyle\"\n            android:layout_height=\"wrap_content\"\n            android:layout_width=\"wrap_content\"/>\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/refresh_recycler_view_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v4.widget.SwipeRefreshLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@id/swipe_refresh_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <android.support.v7.widget.RecyclerView\n        android:id=\"@id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n</android.support.v4.widget.SwipeRefreshLayout>"
  },
  {
    "path": "app/src/main/res/menu/activity_browse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_share\"\n        android:actionProviderClass=\"com.actionbarsherlock.widget.ShareActionProvider\"\n        android:showAsAction=\"always\"\n        android:title=\"@string/menu_share\"/>\n    <item\n        android:id=\"@+id/menu_original_url\"\n        android:showAsAction=\"always\"\n        android:title=\"@string/menu_original_url\"/>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    \n\t<item\n\t    android:id=\"@+id/menu_login\"\n\t    android:title=\"@string/menu_login\"\n\t    app:showAsAction=\"always\"/>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/activity_main.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_feedback\"\n        android:orderInCategory=\"100\"\n        android:showAsAction=\"never\"\n        android:icon=\"@drawable/ic_action_email\"\n        android:title=\"@string/menu_feedback\"/>\n    <item\n        android:id=\"@+id/menu_settings\"\n        android:orderInCategory=\"100\"\n        android:showAsAction=\"never\"\n        android:icon=\"@drawable/ic_action_settings\"\n        android:title=\"@string/menu_settings\"/>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/fragment_browse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_share\"\n        app:actionProviderClass=\"android.support.v7.widget.ShareActionProvider\"\n        app:showAsAction=\"always\"\n        android:title=\"@string/menu_share\"/>\n    <item\n        android:id=\"@+id/menu_original_url\"\n        app:showAsAction=\"always\"\n        android:title=\"@string/menu_original_url\"/>\n   \n</menu>"
  },
  {
    "path": "app/src/main/res/menu/fragment_discuss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    \n    <item android:id=\"@+id/menu_refresh\"\n        android:icon=\"@drawable/ic_action_refresh\"\n        android:title=\"@string/menu_refresh\"\n        android:showAsAction=\"always\"/>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/fragment_news.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <item\n        android:id=\"@+id/menu_show_comment\"\n        android:title=\"@string/menu_show_comment\"/>\n    <item\n        android:id=\"@+id/menu_show_article\"\n        android:title=\"@string/menu_show_article\"/>\n    <item\n        android:id=\"@+id/menu_up_vote\"\n        android:title=\"@string/menu_up_vote\"/>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/values/analytics.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"TypographyDashes\">\n\n    <!-- 开发过程屏蔽统计 -->\n    <string name=\"ga_trackingId\">UA-39500428-1</string>\n    <bool name=\"ga_autoActivityTracking\">true</bool>\n    <bool name=\"ga_reportUncaughtExceptions\">true</bool>\n    <integer name=\"ga_sessionTimeout\">120</integer>\n    <integer name=\"ga_dispatchInterval\">120</integer>\n    <string name=\"ga_appVersion\">1.0.2.0</string>\n    <string name=\"ga_appName\">StartupNews</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     File created by the Android Action Bar Style Generator\n\n     Copyright (C) 2012 readyState Software Ltd\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n  \n          http://www.apache.org/licenses/LICENSE-2.0\n  \n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<resources>\n\n    <!-- A light Holo shade of blue -->\n    <color name=\"holo_blue_light\">#ff33b5e5</color>\n    <!-- A light Holo shade of green -->\n    <color name=\"holo_green_light\">#ff99cc00</color>\n    <!-- A light Holo shade of red -->\n    <color name=\"holo_red_light\">#ffff4444</color>\n    <!-- A dark Holo shade of blue -->\n    <color name=\"holo_blue_dark\">#ff0099cc</color>\n    <!-- A dark Holo shade of green -->\n    <color name=\"holo_green_dark\">#ff669900</color>\n    <!-- A dark Holo shade of red -->\n    <color name=\"holo_red_dark\">#ffcc0000</color>\n    <!-- A Holo shade of purple -->\n    <color name=\"holo_purple\">#ffaa66cc</color>\n    <!-- A light Holo shade of orange -->\n    <color name=\"holo_orange_light\">#ffffbb33</color>\n    <!-- A dark Holo shade of orange -->\n    <color name=\"holo_orange_dark\">#ffff8800</color>\n    <!-- A really bright Holo shade of blue -->\n    <color name=\"holo_blue_bright\">#ff00ddff</color>\n\n    <drawable name=\"ab_bg_black\">#aa000000</drawable>\n\n    <color name=\"colorPrimary\">@android:color/holo_blue_light</color>\n    <color name=\"colorPrimaryDark\">@android:color/holo_blue_dark</color>\n    <color name=\"colorAccent\">@android:color/holo_blue_light</color>\n\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- sliding layer -->\n    <dimen name=\"shadow_width\">8dp</dimen>\n    <dimen name=\"layer_width\">500dp</dimen>\n    <dimen name=\"offset_width\">0dp</dimen>\n\n    <!-- UI elements -->\n    <dimen name=\"whats_on_height\">64dp</dimen>\n    <dimen name=\"whats_on_item_padding\">10dp</dimen>\n    <dimen name=\"whats_on_button_right_padding\">20dp</dimen>\n    <dimen name=\"tab_height\">48dp</dimen>\n    <dimen name=\"track_icon_width\">32dp</dimen>\n    <dimen name=\"track_icon_padding\">2dp</dimen>\n    <dimen name=\"page_margin_width\">16dp</dimen>\n    <dimen name=\"list_extra_button_width\">48dp</dimen>\n    <dimen name=\"action_button_min_width\">56dp</dimen>\n    <dimen name=\"multipane_half_padding\">8dp</dimen>\n    <dimen name=\"multipane_padding\">16dp</dimen>\n    <dimen name=\"list_scroll_top_offset\">16dp</dimen>\n    <dimen name=\"schedule_block_time_width\">72dp</dimen>\n    <dimen name=\"map_multipane_right_padding\">48dp</dimen>\n    <dimen name=\"announcements_margin_width\">8dp</dimen>\n\n    <!-- body content -->\n    <dimen name=\"element_spacing_normal\">8dp</dimen>\n    <dimen name=\"content_padding_normal\">16dp</dimen>\n    <dimen name=\"text_size_small\">12sp</dimen>\n    <dimen name=\"text_size_medium\">14sp</dimen>\n    <dimen name=\"text_size_large\">18sp</dimen>\n    <dimen name=\"text_size_xlarge\">18sp</dimen>\n    <dimen name=\"text_size_diff_large_small\">6sp</dimen>\n    <dimen name=\"speaker_image_size\">48dp</dimen>\n    <dimen name=\"speaker_image_padding\">8dp</dimen>\n    <dimen name=\"vendor_image_size\">100dp</dimen>\n\n    <!-- Google+ -->\n    <dimen name=\"stream_name_text_size\">16sp</dimen>\n    <dimen name=\"stream_original_author_name_text_size\">14sp</dimen>\n    <dimen name=\"stream_content_text_size\">14sp</dimen>\n    <dimen name=\"stream_feedback_text_size\">12sp</dimen>\n    <dimen name=\"stream_fav_icon_size\">14dip</dimen>\n\n    <!-- Widget -->\n    <dimen name=\"widget_margin\">8dp</dimen>\n\n    <dimen name=\"card_insets\">5dp</dimen>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"pull_refresh_list\" type=\"id\"/>\n    <item name=\"menu_logout\" type=\"id\"/>\n    <item name=\"menu_login\" type=\"id\"/>\n\n    <item name=\"drawer_layout\" type=\"id\"/>\n    <item name=\"left_drawer_list\" type=\"id\"/>\n    \n    <item name=\"browse_back\" type=\"id\"/>\n    <item name=\"browse_forward\" type=\"id\"/>\n    <item name=\"browse_readability\" type=\"id\"/>\n    <item name=\"browse_refresh\" type=\"id\"/>\n    <item name=\"browse_website\" type=\"id\"/>\n    <item name=\"browse_webview\" type=\"id\"/>\n\n    <item name=\"swipe_refresh_layout\" type=\"id\"/>\n    <item name=\"recycler_view\" type=\"id\"/>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/integers.xml",
    "content": "<!--\n  Copyright 2012 Google Inc.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n  -->\n\n<resources>\n    <integer name=\"text_header_max_lines\">3</integer>\n    <integer name=\"track_abstract_max_lines\">2</integer>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n\n    <!--<string name=\"app_name\">StartupNews</string>-->\n    <string name=\"app_description\">An Android Startup News Client!</string>\n    <string name=\"hello_world\">Hello World!</string>\n\n    <string-array name=\"section_titles\">\n        <item>热门文章</item>\n        <item>最新文章</item>\n        <item>最新评论</item>\n    </string-array>\n\n    <string name=\"google_play_url\">http://play.google.com/store/apps/details?id=com.halzhang.android.apps.startupnews</string>\n    <string name=\"host\">http://news.dbanotes.net<xliff:g id=\"PATH\">%1$s</xliff:g></string>\n    <string name=\"vote_url\">http://news.dbanotes.net/vote?for=<xliff:g id=\"ITEM_ID\">%1$s</xliff:g>&amp;dir=up&amp;by=<xliff:g id=\"USERID\">%2$s</xliff:g>&amp;auth=<xliff:g id=\"USER\">%3$s</xliff:g>&amp;whence=news</string>\n    <string name=\"comment_artist_title\">on:<xliff:g id=\"TITLE\">%1$s</xliff:g></string>\n    <string name=\"news_subtext\"><xliff:g id=\"POINTS\">%1$d</xliff:g> points · <xliff:g id=\"COMMENTS\">%2$d</xliff:g> comments</string>\n    <string name=\"username\">用户名：</string>\n    <string name=\"password\">密码：</string>\n    <string name=\"error\">哎呀，出错了!</string>\n    <string name=\"error_noemailapp\">不好意思，你未安装邮件客户端，无法通过邮件反馈。您可以通过PC发送反馈邮件到【ghanguo@gmail.com】。非常感谢!</string>\n    <string name=\"error_message\">操作失败:<xliff:g id=\"MSG\">%1$s</xliff:g></string>\n    <string name=\"working\">开发中~~~</string>\n    <string name=\"hint_add_comment\">添加评论</string>\n    <string name=\"weibo_share_suffix\">( 分享自StartupNews:http://play.google.com/store/apps/details?id=com.halzhang.android.apps.startupnews )</string>\n    <string name=\"weibo_package_name\">com.sina.weibo</string>\n    <string name=\"pull_to_refresh_from_bottom_pull_label\">上拉刷新</string>\n    <string name=\"feedback\">反馈</string>\n    <string name=\"drawer_opened\">打开</string>\n    <string name=\"drawer_closed\">关闭</string>\n    <!-- menu string -->\n    <string name=\"menu_share\">分享</string>\n    <string name=\"menu_settings\">设置</string>\n    <string name=\"menu_about\">关于</string>\n    <string name=\"menu_feedback\">反馈</string>\n    <string name=\"menu_refresh\">刷新</string>\n    <string name=\"menu_back\">后退</string>\n    <string name=\"menu_forward\">前进</string>\n    <string name=\"menu_readabilily\">阅读</string>\n    <string name=\"menu_website\">用浏览器打开</string>\n    <string name=\"menu_show_comment\">查看评论</string>\n    <string name=\"menu_show_article\">查看原文</string>\n    <string name=\"menu_up_vote\">顶一下</string>\n    <string name=\"menu_login\">登录</string>\n    <string name=\"menu_logout\">注销</string>\n    <string name=\"menu_original_url\">原网页</string>\n    <string name=\"menu_screenorientation\">横竖屏切换</string>\n\n    <!-- title string -->\n    <string name=\"title_about\">关于</string>\n    <string name=\"title_settings\">设置</string>\n    <string name=\"title_shareto_startupnews\">分享到StartupNews</string>\n    <string name=\"title_discuss\">查看评论</string>\n    <string name=\"title_login\">用户登录</string>\n\n    <!-- preferences string -->\n    <string name=\"pref_title_html_provider\">网页阅读模式</string>\n    <string name=\"pref_key_html_provider\">pref_key_html_provider</string>\n    <string name=\"pref_title_athalzhang\">\\@HalZhang</string>\n    <string name=\"pref_summary_athalzhang\">访问作者微博</string>\n    <string name=\"pref_title_startupnews\">StartupNews</string>\n    <string name=\"pref_summary_startupnews\">访问StartupNews网站</string>\n    <string name=\"pref_key_cookie\">pref_key_cookie</string>\n    <string name=\"pref_key_default_browse\">pref_key_default_browse</string>\n    <string name=\"pref_title_default_browse\">使用内置浏览器</string>\n    <string name=\"pref_summary_on_default_browse\">用内置浏览器浏览文章</string>\n    <string name=\"pref_summary_off_default_browse\">用系统浏览器浏览文章</string>\n    <string name=\"pref_title_opensrouce\">开放源代码许可</string>\n    <string name=\"pref_key_version\">pref_key_version</string>\n    <string name=\"pref_title_version\">版本</string>\n    <string name=\"pref_summary_version\">V<xliff:g id=\"VERSION_NAME\">%1$s</xliff:g></string>\n    <string name=\"pref_title_donation\">这个APP用的还不错？</string>\n    <string name=\"pref_summary_donation\">你可以向作者捐款</string>\n\n    <string-array name=\"html_provider_entries\">\n        <item>Readabilily</item>\n        <item>新浪</item>\n        <item>百度</item>\n        <item>Google</item>\n        <item>Viewtext</item>\n        <item>原网页</item>\n    </string-array>\n    <string-array name=\"html_provider_entry_values\">\n        <item>http://www.readability.com/m?url=</item>\n        <item>http://weibo.cn/sinaurl?to=m&amp;u=</item>\n        <item>http://gate.baidu.com/tc?from=opentc&amp;src=</item>\n        <item>http://www.google.com/gwt/x?u=</item>\n        <item>http://viewtext.org/article?url=</item>\n        <item></item>\n        <!--占坑-->\n    </string-array>\n\n    <string name=\"default_html_provider\"></string>\n    <string name=\"tip_loading\">加载中&#8230;</string>\n    <string name=\"tip_last_page\">亲，最后一页了&#8230;</string>\n    <string name=\"tip_logout\">注销&#8230;</string>\n    <string name=\"tip_login_success\">登录成功</string>\n    <string name=\"tip_login_failure\">登录失败</string>\n    <string name=\"tip_logout_success\">注销成功</string>\n    <string name=\"tip_logout_failure\">注销失败</string>\n    <string name=\"tip_vote_success\">顶成功了！</string>\n    <string name=\"tip_vote_failure\">投票失败！</string>\n    <string name=\"tip_vote_duplicate\">请不要重复投票</string>\n    <string name=\"tip_cookie_invalid\">用户已退出，请重新登录</string>\n    <string name=\"tip_comment_success\">评论成功!</string>\n    <string name=\"tip_comment_failure\">评论失败!</string>\n\n    <!-- Strings related to login -->\n    <string name=\"prompt_username\">用户名</string>\n    <string name=\"prompt_password\">密码</string>\n    <string name=\"action_sign_in_register\">登陆</string>\n    <string name=\"action_sign_in_short\">Sign in</string>\n    <string name=\"menu_forgot_password\">Recover lost password</string>\n    <string name=\"login_progress_signing_in\">登录中&#8230;</string>\n    <string name=\"login_progress_init\">初始化登陆参数&#8230;</string>\n    <string name=\"error_invalid_username\">This username is invalid</string>\n    <string name=\"error_invalid_password\">This password is too short</string>\n    <string name=\"error_incorrect_password\">This password is incorrect</string>\n    <string name=\"error_field_required\">不能为空!</string>\n\n    <!-- TODO: Remove or change this placeholder text -->\n    <string name=\"hello_blank_fragment\">Hello blank fragment</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Generated with http://android-holo-colors.com -->\n<resources xmlns:tools=\"http://schemas.android.com/tools\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!-- Application theme. -->\n    <style name=\"AppTheme\" parent=\"AppBaseTheme\">\n        <!-- All customizations that are NOT specific to a particular API-level can go here. -->\n    </style>\n\n    <style name=\"AppPreferenceTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <item name=\"android:colorPrimary\" tools:ignore=\"NewApi\">@color/colorPrimary</item>\n        <item name=\"android:colorPrimaryDark\" tools:ignore=\"NewApi\">@color/colorPrimaryDark</item>\n        <item name=\"android:colorAccent\" tools:ignore=\"NewApi\">@color/colorAccent</item>\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <style name=\"AppBaseTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:colorPrimary\" tools:ignore=\"NewApi\">@color/colorPrimary</item>\n        <item name=\"android:colorPrimaryDark\" tools:ignore=\"NewApi\">@color/colorPrimaryDark</item>\n        <item name=\"android:colorAccent\" tools:ignore=\"NewApi\">@color/colorAccent</item>\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n        <item name=\"android:windowTranslucentNavigation\" tools:ignore=\"NewApi\">false</item>\n        <item name=\"android:fitsSystemWindows\">false</item>\n        <item name=\"android:navigationBarColor\" tools:ignore=\"NewApi\">@android:color/holo_blue_light</item>\n    </style>\n\n    <style name=\"AppActionBar\" parent=\"Widget.AppCompat.ActionBar\">\n        <item name=\"android:titleTextStyle\">@style/AppActionBarTitleText</item>\n        <item name=\"titleTextStyle\">@style/AppActionBarTitleText</item>\n    </style>\n\n    <style name=\"AppActionBarTitleText\">\n        <item name=\"android:textColor\">@android:color/white</item>\n    </style>\n\n    <style name=\"LoginFormContainer\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:padding\">16dp</item>\n    </style>\n\n    <style name=\"borderlessButtonStyle\">\n        <item name=\"android:background\">@drawable/background_boardless</item>\n        <item name=\"android:paddingStart\" tools:ignore=\"NewApi\">4dip</item>\n        <item name=\"android:paddingEnd\" tools:ignore=\"NewApi\">4dip</item>\n        <item name=\"android:minHeight\">48dip</item>\n        <item name=\"android:minWidth\">64dip</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright (C) 2008 The Android Open Source Project\n\n     Licensed under the Apache License, Version 2.0 (the \"License\");\n     you may not use this file except in compliance with the License.\n     You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n     Unless required by applicable law or agreed to in writing, software\n     distributed under the License is distributed on an \"AS IS\" BASIS,\n     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n     See the License for the specific language governing permissions and\n     limitations under the License.\n-->\n\n<!-- This is a primitive example showing the different types of preferences available. -->\n\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <ListPreference\n        android:defaultValue=\"@string/default_html_provider\"\n        android:entries=\"@array/html_provider_entries\"\n        android:entryValues=\"@array/html_provider_entry_values\"\n        android:key=\"@string/pref_key_html_provider\"\n        android:title=\"@string/pref_title_html_provider\" />\n\n    <CheckBoxPreference\n        android:key=\"@string/pref_key_default_browse\"\n        android:summaryOff=\"@string/pref_summary_off_default_browse\"\n        android:summaryOn=\"@string/pref_summary_on_default_browse\"\n        android:title=\"@string/pref_title_default_browse\"\n        android:defaultValue=\"true\" />\n\n    <PreferenceScreen\n        android:summary=\"@string/pref_summary_startupnews\"\n        android:title=\"@string/pref_title_startupnews\" >\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"http://news.dbanotes.net\" />\n    </PreferenceScreen>\n    <PreferenceScreen\n        android:summary=\"@string/pref_summary_athalzhang\"\n        android:title=\"@string/pref_title_athalzhang\" >\n        <intent\n            android:action=\"android.intent.action.VIEW\"\n            android:data=\"http://weibo.cn/halzhang\" />\n    </PreferenceScreen>\n\n    <Preference\n        android:key=\"@string/pref_key_version\"\n        android:summary=\"@string/pref_summary_version\"\n        android:title=\"@string/pref_title_version\" />\n    <PreferenceScreen\n            android:title=\"@string/pref_title_donation\"\n            android:summary=\"@string/pref_summary_donation\">\n        <intent\n                android:action=\"android.intent.action.VIEW\"\n                android:data=\"http://me.alipay.com/halzhang\"/>\n    </PreferenceScreen>\n\n</PreferenceScreen>"
  },
  {
    "path": "build.gradle",
    "content": "\napply from: 'buildsystem/dependencies.gradle'\n\n// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n    repositories {\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.1.0'\n        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\nallprojects {\n    repositories {\n        jcenter()\n    }\n}"
  },
  {
    "path": "buildsystem/dependencies.gradle",
    "content": "allprojects {\r\n    repositories {\r\n        jcenter()\r\n    }\r\n}\r\n\r\next {\r\n    //Android\r\n    androidBuildToolsVersion = \"23.0.1\"\r\n    androidMinSdkVersion = 15\r\n    androidTargetSdkVersion = 23\r\n    androidCompileSdkVersion = 23\r\n\r\n    //Libraries\r\n    daggerVersion = '2.0'\r\n    butterKnifeVersion = '7.0.1'\r\n    rxJavaVersion = '1.1.5'\r\n    rxAndroidVersion = '1.2.0'\r\n    gsonVersion = '2.4'\r\n    okHttpVersion = '2.5.0'\r\n    androidSupportVersion = '23.1.1'\r\n    greenDaoVersion = '2.0.0'\r\n    eventBusVersion = '2.4.0'\r\n    retrofitVersion = '1.6.0'\r\n    mvpVersion = '1.0.2'\r\n\r\n    presentationDependencies = [\r\n            daggerCompiler: \"com.google.dagger:dagger-compiler:${daggerVersion}\",\r\n            dagger        : \"com.google.dagger:dagger:${daggerVersion}\",\r\n            butterKnife   : \"com.jakewharton:butterknife:${butterKnifeVersion}\",\r\n            recyclerView  : \"com.android.support:recyclerview-v7:${androidSupportVersion}\",\r\n            rxJava        : \"io.reactivex:rxjava:${rxJavaVersion}\",\r\n            rxAndroid     : \"io.reactivex:rxandroid:${rxAndroidVersion}\",\r\n    ]\r\n\r\n\r\n    domainDependencies = [\r\n            daggerCompiler: \"com.google.dagger:dagger-compiler:${daggerVersion}\",\r\n            dagger        : \"com.google.dagger:dagger:${daggerVersion}\",\r\n            rxJava        : \"io.reactivex:rxjava:${rxJavaVersion}\",\r\n    ]\r\n\r\n    dataDependencies = [\r\n            daggerCompiler     : \"com.google.dagger:dagger-compiler:${daggerVersion}\",\r\n            dagger             : \"com.google.dagger:dagger:${daggerVersion}\",\r\n            okHttp             : \"com.squareup.okhttp:okhttp:${okHttpVersion}\",\r\n            okHttpUrlConnection: \"com.squareup.okhttp:okhttp-urlconnection:${okHttpVersion}\",\r\n            gson               : \"com.google.code.gson:gson:${gsonVersion}\",\r\n            rxJava             : \"io.reactivex:rxjava:${rxJavaVersion}\",\r\n            rxAndroid          : \"io.reactivex:rxandroid:${rxAndroidVersion}\",\r\n            androidAnnotations : \"com.android.support:support-annotations:${androidSupportVersion}\",\r\n            greenDao           : \"de.greenrobot:greendao:${greenDaoVersion}\",\r\n            retrofit           : \"com.squareup.retrofit:retrofit:${retrofitVersion}\"\r\n    ]\r\n\r\n}"
  },
  {
    "path": "changelog.md",
    "content": "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.13)\n* 增加已阅读标记功能\n\n#v0.5.1.0(2013.4.6)\n* 修复“用浏览器打开”偶尔crash问题\n* 修复其他bug\n\n#v0.5.0.0(2013.3.30)\n* 优化内置浏览器功能\n* 增加“用浏览器打开”功能\n* 增加使用Readability，优化阅读体验\n* 修复没有邮件客户端时，反馈crash\n\n#v0.4.0.0(2013.3.23)\n* 增加长按列表查看评论功能\n* 更新launcher icon\n* 更新主题为holo blue\n* 更新列表布局\n* 增加设置默认使用浏览器打开\n\n# v0.3.1.0（2013.3.17）\n* 修复版，屏蔽SubmitActivity\n\n# v0.3.0.0（2013.3.16）\n* 采用ActionBarSherlock重写ActionBar，支持Android 2.3.x+（api-level 10+）\n* 支持多种网页转码浏览服务，新浪（默认）、百度、google、ViewText\n* 更新界面主题，统一采用橙色主题，界面更加友好\n* 增加加载loading提示\n* 修复bug #1 issue #2 issue\n\n#v0.2.0.0\n* 浏览时增加网页进度显示\n* 增加系统分享功能\n* 更新最低api为14\n* 增加about说明页面\n* 更新icon\n\n#v0.1.0.0\n* 支持/news列表\n* 支持/newest列表\n* 支持/newcomment列表\n* 支持new浏览"
  },
  {
    "path": "data/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "data/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'com.neenbedankt.android-apt'\n\nandroid {\n    def globalConfiguration = rootProject.extensions.getByName(\"ext\")\n\n    compileSdkVersion globalConfiguration.getAt(\"androidCompileSdkVersion\")\n    buildToolsVersion globalConfiguration.getAt(\"androidBuildToolsVersion\")\n\n    defaultConfig {\n        minSdkVersion globalConfiguration.getAt(\"androidMinSdkVersion\")\n        targetSdkVersion globalConfiguration.getAt(\"androidTargetSdkVersion\")\n        versionCode 1\n        versionName \"1.0\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n\n    def dataDependencies = rootProject.ext.dataDependencies\n    compile fileTree(dir: 'libs', include: ['*.jar'])\n    compile dataDependencies.rxJava\n    compile dataDependencies.rxAndroid\n    compile dataDependencies.okHttp\n    compile dataDependencies.okHttpUrlConnection\n    compile dataDependencies.gson\n    compile dataDependencies.androidAnnotations\n    apt dataDependencies.daggerCompiler\n    compile dataDependencies.dagger\n    provided 'org.glassfish:javax.annotation:10.0-b28'\n\n    compile 'org.jsoup:jsoup:1.7.2'\n\n}\n"
  },
  {
    "path": "data/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/Hal/Developer/android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "data/src/androidTest/java/com/halzhang/android/startupnews/data/ApplicationTest.java",
    "content": "package com.halzhang.android.startupnews.data;\n\nimport android.app.Application;\nimport android.test.ApplicationTestCase;\n\n/**\n * <a href=\"http://d.android.com/tools/testing/testing_android.html\">Testing Fundamentals</a>\n */\npublic class ApplicationTest extends ApplicationTestCase<Application> {\n    public ApplicationTest() {\n        super(Application.class);\n    }\n}"
  },
  {
    "path": "data/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.halzhang.android.startupnews.data\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        >\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/Constant.java",
    "content": "package com.halzhang.android.startupnews.data;\n\n/**\n * 常量\n * Created by Hal on 2016/5/30.\n */\npublic class Constant {\n\n    public static final String SCHEME = \"http\";\n\n    public static final String HOST = \"news.dbanotes.net\";\n\n    public static final String BASE_URL = SCHEME + \"://\" + HOST;\n\n    public static final String NEWS_URL = BASE_URL + \"/news\";\n\n    public static final String LOGIN_URL = BASE_URL + \"/y\";\n\n    public static final String VOTE_URL = BASE_URL + \"/vote\";\n\n    public static final String COMMENT_URL = BASE_URL + \"/r\";\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/CookieFactoryModule.java",
    "content": "package com.halzhang.android.startupnews.data;\n\nimport com.halzhang.android.startupnews.data.utils.CookieFactoryImpl;\nimport com.halzhang.android.startupnews.data.utils.OkHttpClientHelper;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Singleton;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 16/8/14.\n */\n@Module\npublic class CookieFactoryModule {\n\n    @Singleton\n    @Provides\n    OkHttpClientHelper.CookieFactory provideCookieFactory(SessionManager sessionManager) {\n        return new CookieFactoryImpl(sessionManager);\n    }\n\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/JsoupConnectorModule.java",
    "content": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews.data.net.JsoupConnector;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Singleton;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 16/5/30.\n */\n@Module\npublic class JsoupConnectorModule {\n\n    @Singleton\n    @Provides\n    JsoupConnector provideJsoupConnector(Context context, SessionManager sessionManager) {\n        return new JsoupConnector(context, sessionManager);\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/OkHttpClientModule.java",
    "content": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews.data.utils.OkHttpClientHelper;\nimport com.squareup.okhttp.OkHttpClient;\n\nimport javax.inject.Singleton;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 2016/5/30.\n */\n@Module\npublic class OkHttpClientModule {\n\n    @Singleton\n    @Provides\n    OkHttpClient provideOkHttpClient(Context context, OkHttpClientHelper.CookieFactory cookieFactory) {\n        OkHttpClientHelper okHttpClientHelper = new OkHttpClientHelper(cookieFactory, context);\n        return okHttpClientHelper.getOkHttpClient();\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/SessionManagerModule.java",
    "content": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport javax.inject.Singleton;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Module provide {@link SessionManager}\n * Created by Hal on 16/5/30.\n */\n@Module\npublic class SessionManagerModule {\n\n    @Singleton\n    @Provides\n    SessionManager provideSessionManager(Context context) {\n        return new SessionManager(context);\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/SnApiModule.java",
    "content": "package com.halzhang.android.startupnews.data;\n\nimport android.content.Context;\n\nimport com.halzhang.android.startupnews.data.net.ISnApi;\nimport com.halzhang.android.startupnews.data.net.JsoupConnector;\nimport com.halzhang.android.startupnews.data.net.SnApiImpl;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\nimport com.squareup.okhttp.OkHttpClient;\n\nimport javax.inject.Singleton;\n\nimport dagger.Module;\nimport dagger.Provides;\n\n/**\n * Created by Hal on 2016/5/30.\n */\n@Module\npublic class SnApiModule {\n\n    @Singleton\n    @Provides\n    ISnApi provideSnApi(OkHttpClient okHttpClient, Context context, SessionManager sessionManager, JsoupConnector jsoupConnector) {\n        return new SnApiImpl(okHttpClient, context, sessionManager, jsoupConnector);\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNComment.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializable;\n\n/**\n * StartupNews\n * <p>\n * 评论\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 7, 2013\n */\npublic class SNComment implements Serializable {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = 5194737515600100830L;\n\n    private String linkURL;\n\n    private String parentURL;\n\n    private String discussURL;\n\n    private String text;\n\n    private String created;\n\n    private SNUser user;\n\n    private String artistTitle;//文章标题\n    \n    private String voteURL;\n    \n    private String replayURL;\n    \n    public SNComment(){}\n\n    public SNComment(String linkURL, String parentURL, String discussURL, String text,\n            String created, SNUser user, String artistTitle, String voteURL,String replayURL) {\n        super();\n        this.linkURL = linkURL;\n        this.parentURL = parentURL;\n        this.discussURL = discussURL;\n        this.text = text;\n        this.created = created;\n        this.user = user;\n        this.artistTitle = artistTitle;\n        this.voteURL = voteURL;\n        this.replayURL = replayURL;\n    }\n\n    public String getLinkURL() {\n        return linkURL;\n    }\n\n    public void setLinkURL(String linkURL) {\n        this.linkURL = linkURL;\n    }\n\n    public String getParentURL() {\n        return parentURL;\n    }\n\n    public void setParentURL(String parentURL) {\n        this.parentURL = parentURL;\n    }\n\n    public String getDiscussURL() {\n        return discussURL;\n    }\n\n    public void setDiscussURL(String discussURL) {\n        this.discussURL = discussURL;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text = text;\n    }\n\n    public SNUser getUser() {\n        return user;\n    }\n\n    public void setUser(SNUser user) {\n        this.user = user;\n    }\n\n    public String getCreated() {\n        return created;\n    }\n\n    public void setCreated(String created) {\n        this.created = created;\n    }\n\n    public String getArtistTitle() {\n        return artistTitle;\n    }\n\n    public void setArtistTitle(String artistTitle) {\n        this.artistTitle = artistTitle;\n    }\n\n    public String getVoteURL() {\n        return voteURL;\n    }\n\n    public void setVoteURL(String voteURL) {\n        this.voteURL = voteURL;\n    }\n\n    public String getReplayURL() {\n        return replayURL;\n    }\n\n    public void setReplayURL(String replayURL) {\n        this.replayURL = replayURL;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNComments.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 18, 2013\n */\npublic class SNComments implements Serializable {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = -2397354673428961724L;\n\n    private ArrayList<SNComment> snComments = new ArrayList<SNComment>(32);\n\n    private String moreURL;\n\n    public ArrayList<SNComment> getSnComments() {\n        return snComments;\n    }\n\n    public void setSnComments(ArrayList<SNComment> snComments) {\n        this.snComments = snComments;\n    }\n\n    public String getMoreURL() {\n        return moreURL;\n    }\n\n    public void setMoreURL(String moreURL) {\n        this.moreURL = moreURL;\n    }\n    \n    public void clear(){\n        snComments.clear();\n    }\n    \n    public void addComment(SNComment comment){\n        if(comment != null){\n            snComments.add(comment);\n        }\n    }\n    \n    public void addComments(ArrayList<SNComment> comments){\n        if(comments != null && comments.size() > 0){\n            snComments.addAll(comments);\n        }\n    }\n    \n    public int size(){\n        return snComments.size();\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNDiscuss.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 19, 2013\n */\npublic class SNDiscuss implements Serializable {\n    private static final long serialVersionUID = 5814294611810270990L;\n\n    private SNNew snNew;\n\n    private ArrayList<SNComment> mComments = new ArrayList<SNComment>();\n\n    private String fnid;// comment hidden field\n\n    public SNNew getSnNew() {\n        return snNew;\n    }\n\n    public void setSnNew(SNNew snNew) {\n        this.snNew = snNew;\n    }\n\n    public ArrayList<SNComment> getComments() {\n        return mComments;\n    }\n\n    public void setComments(ArrayList<SNComment> comments) {\n        this.mComments = comments;\n    }\n\n    public String getFnid() {\n        return fnid;\n    }\n\n    public void setFnid(String fnid) {\n        this.fnid = fnid;\n    }\n\n    public int commentSize() {\n        return mComments.size();\n    }\n\n    public void addComments(ArrayList<SNComment> comments) {\n        if (comments != null && comments.size() > 0) {\n            this.mComments.addAll(comments);\n        }\n    }\n\n    public void addComment(SNComment comment) {\n        if (comment != null) {\n            mComments.add(comment);\n        }\n    }\n    \n    public void clearComments(){\n        mComments.clear();\n    }\n\n    /**\n     * 数据拷贝\n     * \n     * @param discuss\n     */\n    public void copy(SNDiscuss discuss) {\n        if (discuss != null && discuss != this) {\n            addComments(discuss.getComments());\n            setFnid(discuss.getFnid());\n            setSnNew(discuss.getSnNew());\n        }\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNFeed.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 18, 2013\n */\npublic class SNFeed implements Parcelable {\n\n    private static final long serialVersionUID = 5171992791865009372L;\n\n    private ArrayList<SNNew> mSnNews = new ArrayList<SNNew>(0);\n\n    private String mMoreUrl;\n\n    public ArrayList<SNNew> getSnNews() {\n        return mSnNews;\n    }\n\n    public void setSnNews(ArrayList<SNNew> mSnNews) {\n        this.mSnNews = mSnNews;\n    }\n\n    public String getMoreUrl() {\n        return mMoreUrl;\n    }\n\n    public void setMoreUrl(String mMoreUrl) {\n        this.mMoreUrl = mMoreUrl;\n    }\n\n    public void addNew(SNNew snNew) {\n        if (snNew != null) {\n            mSnNews.add(snNew);\n        }\n    }\n\n    public void addNews(ArrayList<SNNew> news) {\n        if (news != null && news.size() > 0) {\n            mSnNews.addAll(news);\n        }\n    }\n\n    public void clear() {\n        mSnNews.clear();\n    }\n    \n    public int size(){\n        return mSnNews.size();\n    }\n\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeList(this.mSnNews);\n        dest.writeString(this.mMoreUrl);\n    }\n\n    public SNFeed() {\n    }\n\n    protected SNFeed(Parcel in) {\n        this.mSnNews = new ArrayList<SNNew>();\n        in.readList(this.mSnNews, SNNew.class.getClassLoader());\n        this.mMoreUrl = in.readString();\n    }\n\n    public static final Creator<SNFeed> CREATOR = new Creator<SNFeed>() {\n        @Override\n        public SNFeed createFromParcel(Parcel source) {\n            return new SNFeed(source);\n        }\n\n        @Override\n        public SNFeed[] newArray(int size) {\n            return new SNFeed[size];\n        }\n    };\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNNew.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.text.TextUtils;\n\nimport java.io.Serializable;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 7, 2013\n */\npublic class SNNew implements Parcelable {\n\n    private String url;\n\n    private String title;\n\n    private String urlDomain;\n\n    private String voteURL;\n\n    private int points;\n\n    private int commentsCount;\n\n    private String subText;\n\n    private String discussURL;\n\n    private SNUser user;\n\n    private String postID;\n\n    private boolean isDiscuss = false;// 是否是討論貼\n\n    private String text;// 讨论帖内容\n\n    private String createat;\n\n    public SNNew() {\n    }\n\n    public SNNew(String url, String title, String urlDomain, String voteURL, int points,\n            int commentsCount, String subText, String discussURL, SNUser user, String postID,\n            boolean isDiscuss, String text, String createat) {\n        super();\n        this.url = url;\n        this.title = title;\n        this.urlDomain = urlDomain;\n        this.voteURL = voteURL;\n        this.points = points;\n        this.commentsCount = commentsCount;\n        this.subText = subText;\n        this.discussURL = discussURL;\n        this.user = user;\n        this.postID = postID;\n        this.isDiscuss = isDiscuss;\n        this.text = text;\n        this.createat = createat;\n    }\n\n    public SNNew(String url, String title, String urlDomain, String voteURL, int points,\n            int commentsCount, String subText, String discussURL, SNUser user, String postID,\n            String createat) {\n        super();\n        this.url = url;\n        this.title = title;\n        // this.urlDomain = urlDomain;\n        setUrlDomain(urlDomain);\n        this.voteURL = voteURL;\n        this.points = points;\n        this.commentsCount = commentsCount;\n        this.subText = subText;\n        this.discussURL = discussURL;\n        this.user = user;\n        this.postID = postID;\n        this.createat = createat;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public String getComHead() {\n        if (TextUtils.isEmpty(urlDomain)) {\n            return null;\n        }\n        return urlDomain.substring(1, urlDomain.length() - 1);\n    }\n\n    public void setComHead(String comHead) {\n        this.urlDomain = comHead;\n    }\n\n    public String getSubText() {\n        return subText;\n    }\n\n    public void setSubText(String subText) {\n        this.subText = subText;\n    }\n\n    public SNUser getUser() {\n        return user;\n    }\n\n    public void setUser(SNUser user) {\n        this.user = user;\n    }\n\n    public String getUrlDomain() {\n        return urlDomain;\n    }\n\n    public void setUrlDomain(String urlDomain) {\n        this.urlDomain = urlDomain;\n        // news里面的讨论帖\n        this.isDiscuss = this.urlDomain != null && this.urlDomain.endsWith(\"dbanotes.net\");\n    }\n\n    public String getVoteURL() {\n        return voteURL;\n    }\n\n    public void setVoteURL(String voteURL) {\n        this.voteURL = voteURL;\n    }\n\n    public int getCommentsCount() {\n        return commentsCount;\n    }\n\n    public void setCommentsCount(int commentsCount) {\n        this.commentsCount = commentsCount;\n    }\n\n    public String getDiscussURL() {\n        return discussURL;\n    }\n\n    public void setDiscussURL(String discussURL) {\n        this.discussURL = discussURL;\n    }\n\n    public String getPostID() {\n        return postID;\n    }\n\n    public void setPostID(String postID) {\n        this.postID = postID;\n    }\n\n    public int getPoints() {\n        return points;\n    }\n\n    public void setPoints(int points) {\n        this.points = points;\n    }\n\n    public boolean isDiscuss() {\n        return isDiscuss;\n    }\n\n    public void setDiscuss(boolean isDiscuss) {\n        this.isDiscuss = isDiscuss;\n    }\n\n    public String getText() {\n        return text;\n    }\n\n    public void setText(String text) {\n        this.text = text;\n    }\n\n    public String getCreateat() {\n        return createat;\n    }\n\n    public void setCreateat(String createat) {\n        this.createat = createat;\n    }\n\n    @Override\n    public String toString() {\n\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"URL: \").append(url).append(\" Title: \").append(title).append(\" SubText: \")\n                .append(subText).append(\" Comhead: \").append(urlDomain).append(\" DiscussURL: \")\n                .append(discussURL).append(\" isDiscuss: \").append(isDiscuss).append(\" ID:\")\n                .append(postID).append(\" UpVoteUrl:\").append(voteURL);\n        return builder.toString();\n    }\n\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeString(this.url);\n        dest.writeString(this.title);\n        dest.writeString(this.urlDomain);\n        dest.writeString(this.voteURL);\n        dest.writeInt(this.points);\n        dest.writeInt(this.commentsCount);\n        dest.writeString(this.subText);\n        dest.writeString(this.discussURL);\n        dest.writeSerializable(this.user);\n        dest.writeString(this.postID);\n        dest.writeByte(isDiscuss ? (byte) 1 : (byte) 0);\n        dest.writeString(this.text);\n        dest.writeString(this.createat);\n    }\n\n    protected SNNew(Parcel in) {\n        this.url = in.readString();\n        this.title = in.readString();\n        this.urlDomain = in.readString();\n        this.voteURL = in.readString();\n        this.points = in.readInt();\n        this.commentsCount = in.readInt();\n        this.subText = in.readString();\n        this.discussURL = in.readString();\n        this.user = (SNUser) in.readSerializable();\n        this.postID = in.readString();\n        this.isDiscuss = in.readByte() != 0;\n        this.text = in.readString();\n        this.createat = in.readString();\n    }\n\n    public static final Creator<SNNew> CREATOR = new Creator<SNNew>() {\n        @Override\n        public SNNew createFromParcel(Parcel source) {\n            return new SNNew(source);\n        }\n\n        @Override\n        public SNNew[] newArray(int size) {\n            return new SNNew[size];\n        }\n    };\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNSession.java",
    "content": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializable;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Apr 23, 2013\n */\npublic class SNSession implements Serializable {\n\n    private static final long serialVersionUID = 8793748185188606413L;\n\n    private String user;\n\n    private String id;\n\n    public SNSession() {\n    }\n\n    public SNSession(String user, String id) {\n        super();\n        this.user = user;\n        this.id = id;\n    }\n\n    public String getUser() {\n        return user;\n    }\n\n    public void setUser(String user) {\n        this.user = user;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public void clear() {\n        user = null;\n        id = null;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/SNUser.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.entity;\n\nimport java.io.Serializable;\n\n/**\n * StartupNews\n * <p>\n * 用户信息\n * </p>\n * user: jkf created: 9 days ago karma: 17 about:\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 7, 2013\n */\npublic class SNUser implements Serializable {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = -6123600100177508491L;\n\n    private String id;\n    \n    private String name;\n\n    private String created;\n\n    private String karma;\n\n    private String about;\n\n    public SNUser() {\n\n    }\n\n    public SNUser(String id, String created, String karma, String about) {\n        super();\n        this.id = id;\n        this.created = created;\n        this.karma = karma;\n        this.about = about;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getCreated() {\n        return created;\n    }\n\n    public void setCreated(String created) {\n        this.created = created;\n    }\n\n    public String getKarma() {\n        return karma;\n    }\n\n    public void setKarma(String karma) {\n        this.karma = karma;\n    }\n\n    public String getAbout() {\n        return about;\n    }\n\n    public void setAbout(String about) {\n        this.about = about;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n    \n    \n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/entity/Status.java",
    "content": "package com.halzhang.android.startupnews.data.entity;\n\n/**\n * Created by Hal on 16/6/3.\n */\npublic class Status {\n\n    /**\n     * 操作失败\n     */\n    public static final int CODE_FAILURE = -1;\n    /**\n     * 操作成功\n     */\n    public static final int CODE_SUCCESS = 1;\n\n    /**\n     * cookie 无效，需要重新登录\n     */\n    public static final int CODE_COOKIE_VALID = 10;\n\n    /**\n     * 重复操作\n     */\n    public static final int CODE_REPEAT = 20;\n\n\n    public int code;\n    public String message;\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/exception/LoginException.java",
    "content": "package com.halzhang.android.startupnews.data.exception;\n\n/**\n * 登录异常\n * Created by Hal on 15/12/3.\n */\npublic class LoginException extends Exception {\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/exception/NetworkException.java",
    "content": "package com.halzhang.android.startupnews.data.exception;\n\n/**\n * 网络异常\n * Created by Hal on 16/6/5.\n */\npublic class NetworkException extends Exception {\n\n    /**\n     * http status code\n     */\n    private int code;\n\n    public NetworkException(int code, String message) {\n        super(message);\n        this.code = code;\n    }\n\n    public NetworkException(int code, Throwable throwable) {\n        super(throwable);\n        this.code = code;\n    }\n\n    public int getCode() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/exception/SessionExpiredException.java",
    "content": "package com.halzhang.android.startupnews.data.exception;\n\n/**\n * session 过期\n * Created by Hal on 16/8/24.\n */\npublic class SessionExpiredException extends Exception {\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/net/ISnApi.java",
    "content": "package com.halzhang.android.startupnews.data.net;\n\nimport com.halzhang.android.startupnews.data.entity.SNComments;\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;\nimport com.halzhang.android.startupnews.data.entity.SNFeed;\nimport com.halzhang.android.startupnews.data.entity.Status;\n\nimport rx.Observable;\nimport rx.subscriptions.BooleanSubscription;\n\n/**\n * api\n * Created by Hal on 15/11/28.\n */\npublic interface ISnApi {\n\n    /**\n     * 获取新闻列表\n     *\n     * @param url 页面链接\n     * @return {@link SNFeed}\n     */\n    Observable<SNFeed> getSNFeed(String url);\n\n    Observable<String> getFnid();\n\n    Observable<String> login(String fnid, String username, String password);\n\n    Observable<SNComments> getSNComments(String url);\n\n    /**\n     * 投票\n     *\n     * @param postId 文章 id\n     * @return 状态，成功与否\n     */\n    Observable<Status> upVote(String postId);\n\n    /**\n     * 评论\n     *\n     * @param text 评论内容\n     * @param fnid 主题的 find\n     * @return 状态\n     */\n    Observable<Status> comment(String text, String fnid);\n\n    /**\n     * 登出\n     *\n     * @return true 登出成功\n     */\n    Observable<Boolean> logout();\n\n    /**\n     * 获取讨论列表\n     *\n     * @param url\n     */\n    Observable<SNDiscuss> getDiscuss(String url);\n\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/net/JsoupConnector.java",
    "content": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.startupnews.data.net;\n\n\nimport android.content.Context;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\n\nimport javax.inject.Singleton;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Apr 23, 2013\n */\n@Singleton\npublic class JsoupConnector {\n    \n    private static final String LOG_TAG = JsoupConnector.class.getSimpleName();\n\n    private Context mContext;\n\n    private SessionManager mSessionManager;\n\n    public JsoupConnector(Context context, SessionManager sessionManager) {\n        mContext = context;\n        mSessionManager = sessionManager;\n    }\n\n    public Connection newJsoupConnection(String url) {\n        if (TextUtils.isEmpty(url)) {\n            return null;\n        }\n        Connection conn = null;\n        String user = mSessionManager.getSessionUser();\n        if (TextUtils.isEmpty(user)) {\n            Log.i(LOG_TAG, \"user is empty!\");\n            conn = Jsoup.connect(url);\n        } else {\n            conn = Jsoup.connect(url).cookie(\"user\", user);\n        }\n        return conn;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/net/SnApiImpl.java",
    "content": "package com.halzhang.android.startupnews.data.net;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport com.halzhang.android.startupnews.data.Constant;\nimport com.halzhang.android.startupnews.data.entity.SNComments;\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;\nimport com.halzhang.android.startupnews.data.entity.SNFeed;\nimport com.halzhang.android.startupnews.data.entity.Status;\nimport com.halzhang.android.startupnews.data.exception.NetworkException;\nimport com.halzhang.android.startupnews.data.parser.BaseHTMLParser;\nimport com.halzhang.android.startupnews.data.parser.SNCommentsParser;\nimport com.halzhang.android.startupnews.data.parser.SNDiscussParser;\nimport com.halzhang.android.startupnews.data.parser.SNFeedParser;\nimport com.halzhang.android.startupnews.data.utils.SessionManager;\nimport com.squareup.okhttp.FormEncodingBuilder;\nimport com.squareup.okhttp.Headers;\nimport com.squareup.okhttp.HttpUrl;\nimport com.squareup.okhttp.OkHttpClient;\nimport com.squareup.okhttp.Request;\nimport com.squareup.okhttp.RequestBody;\nimport com.squareup.okhttp.Response;\nimport com.squareup.okhttp.internal.http.OkHeaders;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.select.Elements;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.inject.Singleton;\n\nimport rx.Observable;\nimport rx.Subscriber;\nimport rx.functions.Action0;\n\n/**\n * api impl\n * Created by Hal on 2016/5/30.\n */\n@Singleton\npublic class SnApiImpl implements ISnApi {\n\n    private OkHttpClient mOkHttpClient;\n    private Context mContext;\n    private SessionManager mSessionManager;\n    private JsoupConnector mJsoupConnector;\n\n    public SnApiImpl(OkHttpClient okHttpClient, Context context, SessionManager sessionManager, JsoupConnector jsoupConnector) {\n        mOkHttpClient = okHttpClient;\n        mContext = context;\n        mSessionManager = sessionManager;\n        mJsoupConnector = jsoupConnector;\n    }\n\n    @Override\n    public Observable<SNFeed> getSNFeed(final String url) {\n        return Observable.create(new Observable.OnSubscribe<SNFeed>() {\n            @Override\n            public void call(Subscriber<? super SNFeed> subscriber) {\n                try {\n                    Connection conn = mJsoupConnector.newJsoupConnection(url);\n                    Document doc = conn.get();\n                    SNFeedParser parser = new SNFeedParser();\n                    SNFeed feed = parser.parseDocument(doc);\n                    subscriber.onNext(feed);\n                    subscriber.onCompleted();\n                } catch (Exception e) {\n                    subscriber.onError(e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public Observable<String> getFnid() {\n        return Observable.create(new Observable.OnSubscribe<String>() {\n            @Override\n            public void call(Subscriber<? super String> subscriber) {\n                String loginUrl = null;\n                try {\n                    Document doc = Jsoup.connect(Constant.NEWS_URL).get();\n                    if (doc != null) {\n                        Elements loginElements = doc.select(\"a:matches(Login/Register)\");\n                        if (loginElements.size() == 1) {\n                            loginUrl = BaseHTMLParser.resolveRelativeSNURL(loginElements.first().attr(\n                                    \"href\"));\n                        }\n                    }\n                    String fnid = null;\n                    if (!TextUtils.isEmpty(loginUrl)) {\n                        doc = Jsoup.connect(loginUrl).get();\n                        if (doc != null) {\n                            Elements inputElements = doc.select(\"input[name=fnid]\");\n                            if (inputElements != null && inputElements.size() > 0) {\n                                fnid = inputElements.first().attr(\"value\");\n                            }\n                        }\n                    }\n                    subscriber.onNext(fnid);\n                    subscriber.onCompleted();\n                } catch (IOException e) {\n                    subscriber.onError(e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public Observable<String> login(final String fnid, final String username, final String password) {\n        return Observable.create(new Observable.OnSubscribe<String>() {\n            @Override\n            public void call(Subscriber<? super String> subscriber) {\n                FormEncodingBuilder builder = new FormEncodingBuilder();\n                builder.addEncoded(\"fnid\", fnid).addEncoded(\"u\", username).addEncoded(\"p\", password);\n                Request request = new Request.Builder().url(Constant.LOGIN_URL).post(builder.build())\n                        .addHeader(\"Accept-Language\", \"zh-cn\")\n                        .addHeader(\"Accept\", \"*/*\")\n                        .addHeader(\"Accept-Encoding\", \"gzip,deflate\")\n                        .addHeader(\"Connection\", \"keep-alive\")\n                        .build();\n                try {\n                    String user = null;\n                    Response response = mOkHttpClient.newCall(request).execute();\n                    if (response.isSuccessful()) {\n                        Map<String, List<String>> cookiesMap = mOkHttpClient.getCookieHandler().get(request.uri(), OkHeaders.toMultimap(request.headers(), null));\n                        if (cookiesMap.size() > 0) {\n                            List<String> cookies = cookiesMap.get(\"Cookie\");\n                            for (String s : cookies) {\n                                String[] cookie = TextUtils.split(s, \"=\");\n                                if (cookie.length == 2 && \"user\".equals(cookie[0])) {\n                                    user = cookie[1];\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                    response.body().close();\n                    mSessionManager.storeSession(user, username);\n                    subscriber.onNext(user);\n                    subscriber.onCompleted();\n                } catch (IOException e) {\n                    subscriber.onError(e);\n                }\n\n            }\n        }).doOnSubscribe(new Action0() {\n            @Override\n            public void call() {\n                mSessionManager.clear();\n            }\n        });\n    }\n\n    @Override\n    public Observable<SNComments> getSNComments(final String url) {\n        return Observable.create(new Observable.OnSubscribe<SNComments>() {\n            @Override\n            public void call(Subscriber<? super SNComments> subscriber) {\n                try {\n                    Connection conn = mJsoupConnector.newJsoupConnection(url);\n                    Document doc = conn.get();\n                    SNCommentsParser parser = new SNCommentsParser();\n                    SNComments comments = parser.parseDocument(doc);\n                    subscriber.onNext(comments);\n                    subscriber.onCompleted();\n                } catch (Exception e) {\n                    subscriber.onError(e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public Observable<Status> upVote(final String postId) {\n        return Observable.create(new Observable.OnSubscribe<Status>() {\n            @Override\n            public void call(Subscriber<? super Status> subscriber) {\n                try {\n                    HttpUrl httpUrl = new HttpUrl.Builder()\n                            .scheme(Constant.SCHEME)\n                            .host(Constant.HOST)\n                            .addPathSegment(\"vote\")\n                            .addQueryParameter(\"for\", postId)\n                            .addQueryParameter(\"dir\", \"up\")\n                            .addQueryParameter(\"by\", mSessionManager.getSessionId())\n                            .addQueryParameter(\"auth\", mSessionManager.getSessionUser())\n                            .addQueryParameter(\"whence\", \"news\")\n                            .build();\n                    Request request = new Request.Builder().url(httpUrl).build();\n                    Response response = mOkHttpClient.newCall(request).execute();\n                    Status status = new Status();\n                    if (response.isSuccessful()) {\n                        status.code = Status.CODE_SUCCESS;\n                    } else {\n                        String content = response.body().string();\n\n                        if (content.contains(\"mismatch\")) {\n                            // 用户cookie无效\n                            status.code = Status.CODE_COOKIE_VALID;\n                        } else {\n                            status.code = Status.CODE_REPEAT;\n                        }\n                    }\n                    subscriber.onNext(status);\n                    subscriber.onCompleted();\n                } catch (Exception e) {\n                    subscriber.onError(e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public Observable<Status> comment(final String text, final String fnid) {\n        return Observable.create(new Observable.OnSubscribe<Status>() {\n            @Override\n            public void call(Subscriber<? super Status> subscriber) {\n                try {\n                    RequestBody body = new FormEncodingBuilder().add(\"fnid\", fnid).add(\"text\", text).build();\n                    Request request = new Request.Builder().url(Constant.COMMENT_URL).post(body).build();\n                    Response response = mOkHttpClient.newCall(request).execute();\n                    if (response.isSuccessful()) {\n                        String refreerLocation = null;\n                        Headers headers = response.headers();\n                        for (int i = 0; i < headers.size(); i++) {\n                            if (\"Refreer-Location\".equals(headers.name(i))) {\n                                refreerLocation = headers.value(i);\n                            }\n                        }\n                        Status status = new Status();\n                        if (TextUtils.isEmpty(refreerLocation) || refreerLocation.contains(\"fnid\")) {\n                            //Location:fnid=xxxxx Cookie失效，重新登陆\n                            status.code = Status.CODE_COOKIE_VALID;\n                        } else if (refreerLocation.contains(\"item\")) {\n                            status.code = Status.CODE_SUCCESS;\n                        } else {\n                            status.code = Status.CODE_FAILURE;\n                        }\n                        subscriber.onNext(status);\n                        subscriber.onCompleted();\n                    } else {\n                        NetworkException networkException = new NetworkException(response.code(),\n                                response.message());\n                        subscriber.onError(networkException);\n                    }\n                } catch (Exception e) {\n                    e.printStackTrace();\n                    subscriber.onError(e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public Observable<Boolean> logout() {\n        return Observable.create(new Observable.OnSubscribe<Boolean>() {\n            @Override\n            public void call(Subscriber<? super Boolean> subscriber) {\n                try {\n                    String logoutUrl = null;\n                    Boolean result = false;\n                    Connection conn = mJsoupConnector.newJsoupConnection(Constant.HOST + \"/news\");\n                    Document doc = conn.get();\n                    Elements elements = doc.select(\"a:matches(logout)\");\n                    if (elements.size() > 0) {\n                        logoutUrl = BaseHTMLParser.resolveRelativeSNURL(elements.attr(\"href\"));\n                    } else {\n                        // 用户可能在pc注销了\n                        mSessionManager.clear();\n                        result = true;\n                    }\n                    if (!TextUtils.isEmpty(logoutUrl)) {\n                        Request request = new Request.Builder().url(logoutUrl).build();\n                        Response response = mOkHttpClient.newCall(request).execute();\n                        result = response.isSuccessful();\n                    }\n                    subscriber.onNext(result);\n                    subscriber.onCompleted();\n                } catch (Exception e) {\n                    subscriber.onError(e);\n                }\n            }\n        });\n    }\n\n    @Override\n    public Observable<SNDiscuss> getDiscuss(final String url) {\n        return Observable.create(new Observable.OnSubscribe<SNDiscuss>() {\n            @Override\n            public void call(Subscriber<? super SNDiscuss> subscriber) {\n                try {\n                    Connection conn = mJsoupConnector.newJsoupConnection(url);\n                    Document doc = conn.get();\n                    SNDiscussParser parser = new SNDiscussParser();\n                    SNDiscuss discuss = parser.parseDocument(doc);\n                    subscriber.onNext(discuss);\n                    subscriber.onCompleted();\n                } catch (Exception e) {\n                    subscriber.onError(e);\n                }\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/BaseHTMLParser.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\nimport android.text.TextUtils;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.TextNode;\nimport org.w3c.dom.Node;\n\nimport java.net.URI;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport javax.xml.xpath.XPath;\nimport javax.xml.xpath.XPathConstants;\n\n/**\n * StartupNews\n * <p>\n * html解析\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 18, 2013\n */\npublic abstract class BaseHTMLParser<T> {\n\n    public static final Pattern CREATEAT_PATTERN = Pattern.compile(\"\\\\d{1,2}\\\\s\\\\w+\\\\sago\");\n\n    public static final int UNDEFINED = -1;\n\n    public T parse(String input) throws Exception {\n        return parseDocument(Jsoup.parse(input));\n    }\n\n    public abstract T parseDocument(Document doc) throws Exception;\n\n    public static String getDomainName(String url) {\n        URI uri;\n        try {\n            uri = new URI(url);\n            String domain = uri.getHost();\n            return domain.startsWith(\"www.\") ? domain.substring(4) : domain;\n        } catch (Exception e) {\n            return url;\n        }\n    }\n\n    public static <T extends Object> T getSafe(List<T> list, int index) {\n        if (list.size() - 1 >= index) {\n            return list.get(index);\n        } else {\n            return null;\n        }\n    }\n\n    public static String getFirstTextValueInElementChildren(Element element) {\n        if (element == null) {\n            return \"\";\n        }\n        for (org.jsoup.nodes.Node node : element.childNodes()) {\n            if (node instanceof TextNode) {\n                return ((TextNode) node).text();\n            }\n        }\n        return \"\";\n    }\n\n    public static String getStringValue(String query, Node source, XPath xpath) {\n        try {\n            return ((Node) xpath.evaluate(query, source, XPathConstants.NODE)).getNodeValue();\n        } catch (Exception e) {\n            // TODO insert Google Analytics tracking here?\n        }\n        return \"\";\n    }\n\n    public static Integer getIntValueFollowedBySuffix(String value, String suffix) {\n        if (value == null || suffix == null)\n            return 0;\n\n        int suffixWordIdx = value.indexOf(suffix);\n        if (suffixWordIdx >= 0) {\n            String extractedValue = value.substring(0, suffixWordIdx);\n            try {\n                return Integer.parseInt(extractedValue);\n            } catch (NumberFormatException e) {\n                return UNDEFINED;\n            }\n        }\n        return UNDEFINED;\n    }\n\n    public static String getStringValuePrefixedByPrefix(String value, String prefix) {\n        int prefixWordIdx = value.indexOf(prefix);\n        if (prefixWordIdx >= 0) {\n            return value.substring(prefixWordIdx + prefix.length());\n        }\n        return null;\n    }\n\n    public static String resolveRelativeSNURL(String url) {\n        if (TextUtils.isEmpty(url)) {\n            return null;\n        }\n\n        String snurl = \"http://news.dbanotes.net/\";\n\n        if (url.startsWith(\"http\") || url.startsWith(\"ftp\")) {\n            return url;\n        } else if (url.startsWith(\"/\")) {\n            return snurl + url.substring(1);\n        } else {\n            return snurl + url;\n        }\n    }\n\n    public String getCreateAt(String text) {\n        if (TextUtils.isEmpty(text)) {\n            return null;\n        }\n        Matcher matcher = CREATEAT_PATTERN.matcher(text);\n        if (matcher.find()) {\n            return matcher.group();\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParser.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\nimport com.halzhang.android.startupnews.data.entity.SNComment;\nimport com.halzhang.android.startupnews.data.entity.SNComments;\nimport com.halzhang.android.startupnews.data.entity.SNUser;\n\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\n/**\n * StartupNews\n * <p>\n * 评论\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 18, 2013\n */\npublic class SNCommentsParser extends BaseHTMLParser<SNComments> {\n    \n    \n    public SNCommentsParser(){}\n    \n    @Override\n    public SNComments parseDocument(Document doc) throws Exception {\n        SNComments comments = new SNComments();\n        if (doc == null) {\n            return comments;\n        }\n        Elements tableRows = doc.body().select(\"table tr table tr\");\n        if (tableRows != null && tableRows.size() > 0) {\n            tableRows.remove(0);\n            // 获取下一页链接\n            Elements moreURLElements = tableRows.select(\"a:matches(More)\");\n            String moreURL = null;\n            if (moreURLElements.size() > 0) {\n                moreURL = resolveRelativeSNURL(moreURLElements.attr(\"href\"));\n            }\n            comments.setMoreURL(moreURL);\n            String linkURL = null;\n            String parentURL = null;\n            String discussURL = null;\n            String text = null;\n            String created = null;\n            SNUser user = null;\n            String artistTitle = null;// 文章标题\n            String voteURL = null;\n            for (int row = 0; row < tableRows.size(); row++) {\n                int rowInPost = row % 2;\n                Element rowElement = tableRows.get(row);\n                if (rowInPost == 0) {\n                    Element textElement = rowElement.select(\"tr > td:eq(1) > span\").first();\n                    if (textElement == null) {\n                        break;\n                    }\n                    text = textElement.text();\n                    user = new SNUser();\n                    \n                    Element spanElement = rowElement.select(\"tr > td:eq(1) > div > span\").first();\n                    created = getCreateAt(spanElement.text());\n                    Elements aElements = spanElement.select(\"span > a\");\n                    if (aElements != null && aElements.size() >= 4) {\n                        int size = aElements.size();\n                        Element anthorURLElement = aElements.first();\n                        user.setId(anthorURLElement.text());\n                        Element linkURLElement = aElements.get(1);\n                        linkURL = resolveRelativeSNURL(linkURLElement.attr(\"href\"));\n                        Element parentURLElement = aElements.get(2);\n                        parentURL = resolveRelativeSNURL(parentURLElement.attr(\"href\"));\n                        Element artistAElement = aElements.last();\n                        discussURL = resolveRelativeSNURL(artistAElement.attr(\"href\"));\n                        artistTitle = artistAElement.text();\n                        if (size == 6) {\n                            // TODO edit delete\n                        }\n                    }\n\n                    Element voteAElement = rowElement.select(\"tr > td:eq(0) a\").first();\n                    if (voteAElement != null) {\n                        // 登录用户的评论没有url\n                        voteURL = resolveRelativeSNURL(voteAElement.attr(\"href\"));\n                    }\n                    comments.addComment(new SNComment(linkURL, parentURL, discussURL, text, created,\n                            user, artistTitle, voteURL,null));\n                }\n            }\n        }\n        return comments;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParserV1.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\n\nimport com.halzhang.android.startupnews.data.entity.SNComment;\nimport com.halzhang.android.startupnews.data.entity.SNComments;\nimport com.halzhang.android.startupnews.data.entity.SNUser;\n\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\nimport java.util.Iterator;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 19, 2013\n */\npublic class SNCommentsParserV1 extends BaseHTMLParser<SNComments> {\n\n    @Override\n    public SNComments parseDocument(Document doc) throws Exception {\n        SNComments comments = new SNComments();\n        if (doc == null) {\n            return comments;\n        }\n        Element body = doc.body();\n        Elements commentSpans = body.select(\"span.comment\");\n        Elements comHeadSpans = body.select(\"span.comhead\");\n        if (!commentSpans.isEmpty()) {\n            Iterator<Element> spanCommentIt = commentSpans.iterator();\n            Iterator<Element> spanComHeadIt = comHeadSpans.iterator();\n            SNComment comment = null;\n            SNUser user = null;\n            while (spanComHeadIt.hasNext() && spanCommentIt.hasNext()) {\n                String commentText = spanCommentIt.next().text();\n                Element span = spanComHeadIt.next();\n                Elements as = span.getElementsByTag(\"a\");\n                user = new SNUser();\n                user.setId(as.get(0).text());\n                String link = as.get(1).attr(\"href\");\n                String parent = as.get(2).attr(\"href\");\n                String discuss = as.get(3).attr(\"href\");\n                String title = as.get(3).text();\n                comment = new SNComment();\n                comment.setUser(user);\n                comment.setLinkURL(resolveRelativeSNURL(link));\n                comment.setParentURL(resolveRelativeSNURL(parent));\n                comment.setDiscussURL(resolveRelativeSNURL(discuss));\n                comment.setText(commentText);\n                comment.setArtistTitle(title);\n                comments.addComment(comment);\n            }\n        }\n        Elements moreURLElements = body.select(\"a:matches(More)\");\n        String moreURL = null;\n        if (moreURLElements.size() > 0) {\n            moreURL = resolveRelativeSNURL(moreURLElements.attr(\"href\"));\n        }\n        comments.setMoreURL(moreURL);\n        return comments;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNDiscussParser.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\n\nimport com.halzhang.android.startupnews.data.entity.SNComment;\nimport com.halzhang.android.startupnews.data.entity.SNDiscuss;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\nimport com.halzhang.android.startupnews.data.entity.SNUser;\n\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\n/**\n * StartupNews\n * <p>\n * </p>\n * \n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 19, 2013\n */\npublic class SNDiscussParser extends BaseHTMLParser<SNDiscuss> {\n    @SuppressWarnings(\"unused\")\n    private static final String LOG_TAG = SNDiscussParser.class.getSimpleName();\n\n    @Override\n    public SNDiscuss parseDocument(Document doc) throws Exception {\n        SNDiscuss discuss = new SNDiscuss();\n        if (doc == null) {\n            return discuss;\n        }\n        // news\n        Elements tableElements = doc.select(\"table tr table\");\n        if (tableElements != null && tableElements.size() > 1) {\n            String voteURL = null;\n            String title = null;\n            String url = null;\n            String urlDomain = null;\n            String subText = null;\n            boolean isDiscuss = false;\n            SNUser user = null;\n            int points = 0;\n            int commentsCount = 0;\n            String postID = null;\n            String discussURL = null;\n            String text = null;\n            String createat = null;\n            String fnid = null;\n            Element newsTableElement = tableElements.get(1);\n            Elements trElements = newsTableElement.getElementsByTag(\"tr\");\n            isDiscuss = trElements.size() > 4;// 讨论帖的有6个tr\n            Element titleTrElement = trElements.get(0);\n            Element voteElement = titleTrElement.select(\"tr > td:eq(0) a\").first();\n            if (voteElement != null) {\n                voteURL = resolveRelativeSNURL(voteElement.attr(\"href\"));\n            }\n            Element titleAElement = titleTrElement.select(\"tr > td:eq(1) a\").first();\n            if (titleAElement != null) {\n                /*\n                 * fixed #5 issue “No Activity found to handle Intent { act=android.intent.action.VIEW dat=item?id=2901 }”\n                 * 由于没有对url进行处理，对于讨论帖的标题的a标签的href（item?id=2901）是无法直接打开的\n                 */\n                url = resolveRelativeSNURL(titleAElement.attr(\"href\"));\n                urlDomain = getDomainName(url);\n                title = titleAElement.text();\n            }\n\n            Element subTextTdeElement = trElements.get(1).select(\"td.subtext\").first();\n            subText = subTextTdeElement.html();\n            createat = getCreateAt(subText);\n            points = getIntValueFollowedBySuffix(subTextTdeElement.select(\"td > span\").text(), \" p\");\n\n            String author = subTextTdeElement.select(\"td > a[href*=user]\").text();\n            user = new SNUser();\n            user.setName(author);\n            user.setId(author);\n            Element e2 = subTextTdeElement.select(\"td > a[href*=item]\").first();\n            if (e2 != null) {\n                commentsCount = getIntValueFollowedBySuffix(e2.text(), \" c\");\n                if (commentsCount == BaseHTMLParser.UNDEFINED && e2.text().contains(\"discuss\"))\n                    commentsCount = 0;\n                postID = getStringValuePrefixedByPrefix(e2.attr(\"href\"), \"id=\");\n                discussURL = resolveRelativeSNURL(e2.attr(\"href\"));\n            } else {\n                commentsCount = BaseHTMLParser.UNDEFINED;\n            }\n            if (isDiscuss) {\n                text = trElements.get(3).text();\n                fnid = trElements.get(5).getElementsByTag(\"input\").first().attr(\"value\");\n            } else {\n                fnid = trElements.get(3).getElementsByTag(\"input\").first().attr(\"value\");\n            }\n            discuss.setFnid(fnid);\n            discuss.setSnNew(new SNNew(url, title, urlDomain, voteURL, points, commentsCount,\n                    subText, discussURL, user, postID, isDiscuss, text, createat));\n\n        }\n        Elements tableRows = doc.select(\"table tr table tr table tr\");\n        if (tableRows != null && tableRows.size() > 0) {\n            Element rowElement = null;\n            SNComment comment = null;\n            for (int row = 0; row < tableRows.size(); row++) {\n                comment = new SNComment();\n                rowElement = tableRows.get(row);\n                Element voteAElement = rowElement.select(\"tr > td:eq(1) a\").first();\n                if (voteAElement != null) {\n                    comment.setVoteURL(resolveRelativeSNURL(voteAElement.attr(\"href\")));\n                }\n                Elements aElements = rowElement.select(\"tr > td:eq(2) a\");\n                if(aElements == null || aElements.size() < 1){\n                    continue;\n                }\n                SNUser user = new SNUser();\n                user.setId(aElements.first().text());\n                comment.setUser(user);\n                comment.setLinkURL(resolveRelativeSNURL(aElements.last().attr(\"href\")));\n                comment.setText(rowElement.select(\"tr > td:eq(2) > span\").first().text());\n                comment.setReplayURL(resolveRelativeSNURL(rowElement\n                        .select(\"tr > td:eq(2) a[href^=reply]\").first().attr(\"href\")));\n                discuss.getComments().add(comment);\n            }\n        }\n        return discuss;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/parser/SNFeedParser.java",
    "content": "/**\n * Copyright (C) 2013 HalZhang\n */\n\npackage com.halzhang.android.startupnews.data.parser;\n\nimport android.util.Log;\n\nimport com.halzhang.android.startupnews.data.entity.SNFeed;\nimport com.halzhang.android.startupnews.data.entity.SNNew;\nimport com.halzhang.android.startupnews.data.entity.SNUser;\n\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\nimport java.util.ArrayList;\n\n/**\n * StartupNews\n * <p>\n * news解析\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Mar 18, 2013\n */\npublic class SNFeedParser extends BaseHTMLParser<SNFeed> {\n\n    private static final String LOG_TAG = SNFeedParser.class.getSimpleName();\n\n    @Override\n    public SNFeed parseDocument(Document doc) throws Exception {\n        SNFeed feed = new SNFeed();\n        if (doc == null) {\n            return feed;\n        }\n        long start = System.currentTimeMillis();\n        // Elements loginout = doc.select(\"a:matches(Login/Register|logout)\");\n        // if (loginout.size() > 0) {\n        // String loginoutUrl = resolveRelativeSNURL(loginout.attr(\"href\"));\n        // Log.i(LOG_TAG, \"Login or out url: \" + loginoutUrl);\n        // }\n\n        Elements tableRows = doc.select(\"table tr table tr\");\n        tableRows.remove(0);// 顶部导航\n        // 获取下一页链接\n        Elements moreURLElements = tableRows.select(\"a:matches(More)\");\n        String moreURL = null;\n        if (moreURLElements.size() > 0) {\n            moreURL = resolveRelativeSNURL(moreURLElements.attr(\"href\"));\n        }\n        feed.setMoreUrl(moreURL);\n        ArrayList<SNNew> snNews = new ArrayList<SNNew>(32);\n        String url = null;\n        String title = null;\n        String urlDomain = null;\n        String voteURL = null;\n        int points = 0;\n        int commentsCount = 0;\n        String discussURL = null;\n        String subText = null;\n        SNUser user = null;\n        String postID = null;\n        String createat = null;\n        boolean endParse = false;\n        for (int row = 0; row < tableRows.size(); row++) {\n            int rowInPost = row % 3;\n            Element rowElement = tableRows.get(row);\n            switch (rowInPost) {\n                case 0:\n                    // 标题\n                    Element titleAElement = rowElement.select(\"tr > td:eq(2) > a\").first();\n                    if (titleAElement == null) {\n                        endParse = true;\n                        break;\n                    }\n                    title = titleAElement.text();\n                    url = resolveRelativeSNURL(titleAElement.attr(\"href\"));\n                    urlDomain = getDomainName(url);\n\n                    Element voteAElement = rowElement.select(\"tr > td:eq(1) a\").first();\n                    if (voteAElement != null) {\n                        voteURL = resolveRelativeSNURL(voteAElement.attr(\"href\"));\n                    } else {\n                        voteURL = null;\n                    }\n                    break;\n                case 1:\n                    // 副标题\n                    Element tdElement = rowElement.select(\"tr > td:eq(1)\").first();\n                    subText = tdElement.text();\n                    createat = getCreateAt(subText);\n                    points = getIntValueFollowedBySuffix(tdElement.select(\"td > span\").text(), \" p\");\n\n                    String author = tdElement.select(\"td > a[href*=user]\").text();\n                    user = new SNUser();\n                    user.setName(author);\n                    user.setId(author);\n                    Element e2 = tdElement.select(\"td > a[href*=item]\").first();\n                    if (e2 != null) {\n                        commentsCount = getIntValueFollowedBySuffix(e2.text(), \" c\");\n                        if (commentsCount == BaseHTMLParser.UNDEFINED\n                                && e2.text().contains(\"discuss\"))\n                            commentsCount = 0;\n                        postID = getStringValuePrefixedByPrefix(e2.attr(\"href\"), \"id=\");\n                        discussURL = resolveRelativeSNURL(e2.attr(\"href\"));\n                    } else {\n                        commentsCount = BaseHTMLParser.UNDEFINED;\n                    }\n                    snNews.add(new SNNew(url, title, urlDomain, voteURL, points, commentsCount,\n                            subText, discussURL, user, postID, createat));\n                    break;\n                default:\n                    break;\n            }\n            if (endParse) {\n                break;\n            }\n        }\n        feed.setSnNews(snNews);\n        Log.i(LOG_TAG, \"Take Time:\" + (System.currentTimeMillis() - start));\n        return feed;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/CookieFactoryImpl.java",
    "content": "package com.halzhang.android.startupnews.data.utils;\n\nimport com.halzhang.android.startupnews.data.utils.OkHttpClientHelper.CookieFactory;\n\n/**\n * Created by Hal on 16/8/14.\n */\npublic class CookieFactoryImpl implements CookieFactory {\n\n    private SessionManager mSessionManager;\n\n    public CookieFactoryImpl(SessionManager sessionManager) {\n        mSessionManager = sessionManager;\n    }\n\n    @Override\n    public String getCookie() {\n        return mSessionManager.getCookieString();\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/NetworkUtils.java",
    "content": "package com.halzhang.android.startupnews.data.utils;\r\n\r\nimport android.content.Context;\r\nimport android.net.ConnectivityManager;\r\nimport android.net.NetworkInfo;\r\n\r\n/**\r\n * 网络工具\r\n * Created by Hal on 2015/9/28.\r\n */\r\npublic class NetworkUtils {\r\n\r\n    public static boolean isNetworkAvailable(Context context) {\r\n        ConnectivityManager cm = (ConnectivityManager) context\r\n                .getSystemService(Context.CONNECTIVITY_SERVICE);\r\n        if (cm != null) {\r\n            // 如果仅仅是用来判断网络连接\r\n            // 则可以使用 cm.getActiveNetworkInfo().isAvailable();\r\n            NetworkInfo[] info = cm.getAllNetworkInfo();\r\n            if (info != null) {\r\n                for (NetworkInfo anInfo : info) {\r\n                    if (anInfo.getState() == NetworkInfo.State.CONNECTED) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n}\r\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/OkHttpClientHelper.java",
    "content": "package com.halzhang.android.startupnews.data.utils;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.support.annotation.NonNull;\nimport android.webkit.WebSettings;\n\nimport com.squareup.okhttp.Cache;\nimport com.squareup.okhttp.Interceptor;\nimport com.squareup.okhttp.OkHttpClient;\nimport com.squareup.okhttp.Request;\nimport com.squareup.okhttp.Response;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.CookieManager;\nimport java.net.CookiePolicy;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.inject.Inject;\nimport javax.inject.Singleton;\n\n/**\n * okhttp\n * Created by Hal on 2015/11/12.\n */\n@Singleton\npublic class OkHttpClientHelper {\n\n    public interface CookieFactory {\n        public String getCookie();\n    }\n\n    /**\n     * USER-AGENT\n     */\n    public static final String USER_AGENT = \"Mozilla/5.0 (Linux; Android \" + Build.VERSION.RELEASE\n            + \"; \" + Build.MODEL + \" Build/\" + Build.ID + \")\";\n\n    private static final long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MB\n\n\n    private OkHttpClient mOkHttpClient;\n\n    private CookieFactory mCookieFactory;\n\n    @NonNull\n    private Context mContext;\n\n    @Inject\n    public OkHttpClientHelper(CookieFactory cookieFactory, @NonNull Context context) {\n        mCookieFactory = cookieFactory;\n        mContext = context;\n        init(mContext, mCookieFactory);\n    }\n\n    private void init(final Context context, CookieFactory factory) {\n        mCookieFactory = factory;\n        if (mOkHttpClient == null) {\n            Interceptor mCacheControlInterceptor = new Interceptor() {\n                @Override\n                public Response intercept(Chain chain) throws IOException {\n                    Request request = chain.request();\n                    Request.Builder builder = request.newBuilder();\n\n                    builder.addHeader(\"Accept-Language\", \"zh-cn\");\n                    builder.addHeader(\"Accept\", \"*/*\");\n                    if (mCookieFactory != null) {\n                        builder.addHeader(\"Cookie\", mCookieFactory.getCookie());\n                    }\n                    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {\n                        builder.header(\"User-Agent\", WebSettings.getDefaultUserAgent(context));\n                    } else {\n                        builder.header(\"User-Agent\", USER_AGENT);\n                    }\n\n                    // Add Cache Control only for GET methods\n                    if (request.method().equals(\"GET\")) {\n                        if (NetworkUtils.isNetworkAvailable(context)) {\n                            // 1 day\n                            request.newBuilder().header(\"Cache-Control\", \"only-if-cached\").build();\n                        } else {\n                            // 4 weeks stale\n                            request.newBuilder().header(\"Cache-Control\", \"public, max-stale=2419200\").build();\n                        }\n                    }\n                    Response response = chain.proceed(request);\n                    // Re-write response CC header to force use of cache\n                    return response.newBuilder()\n                            .header(\"Cache-Control\", \"public, max-age=86400\") // 1 day\n                            .build();\n                }\n            };\n            Cache cache = new Cache(new File(context.getCacheDir(), \"http\"), SIZE_OF_CACHE);\n            mOkHttpClient = new OkHttpClient();\n            mOkHttpClient.setCache(cache);\n            mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);\n            mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);\n            // Add Cache-Control Interceptor\n            mOkHttpClient.networkInterceptors().add(mCacheControlInterceptor);\n            //重试\n            mOkHttpClient.interceptors().add(new Interceptor() {\n                @Override\n                public Response intercept(Chain chain) throws IOException {\n                    Request request = chain.request();\n                    // try the request\n                    Response response = chain.proceed(request);\n                    int tryCount = 0;\n                    while (!response.isSuccessful() && tryCount < 3) {\n                        tryCount++;\n                        // retry the request\n                        response = chain.proceed(request);\n                    }\n                    // otherwise just pass the original response on\n                    return response;\n                }\n            });\n            mOkHttpClient.setCookieHandler(new CookieManager(new PersistentCookieStore(context), CookiePolicy.ACCEPT_ALL));\n        }\n    }\n\n    public OkHttpClient getOkHttpClient() {\n        if (mOkHttpClient == null) {\n            throw new RuntimeException(\"okhttp uninit!\");\n        }\n        return mOkHttpClient;\n    }\n\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/PersistentCookieStore.java",
    "content": "/*\n    Android Asynchronous Http Client\n    Copyright (c) 2011 James Smith <james@loopj.com>\n    http://loopj.com\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n*/\n\npackage com.halzhang.android.startupnews.data.utils;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.text.TextUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.net.CookieHandler;\nimport java.net.CookieStore;\nimport java.net.HttpCookie;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * A persistent cookie store which implements the Apache HttpClient\n * {@link CookieStore} interface. Cookies are stored and will persist on the\n * user's device between application sessions since they are serialized and\n * stored in {@link SharedPreferences}.\n * <p/>\n * Instances of this class are designed to be used with\n * {@link com.squareup.okhttp.OkHttpClient#setCookieHandler(CookieHandler)}, but can also be used with a\n * regular old apache HttpClient/HttpContext if you prefer.\n */\npublic class PersistentCookieStore implements CookieStore {\n    private static final String COOKIE_PREFS = \"CookiePrefsFile\";\n    private static final String COOKIE_NAME_PREFIX = \"cookie_\";\n    /**\n     * -host:\n     * --cookieToken:value\n     */\n    private final ConcurrentHashMap<String, ConcurrentHashMap<String, HttpCookie>> cookies;\n    private final SharedPreferences cookiePrefs;\n\n    /**\n     * Construct a persistent cookie store.\n     */\n    public PersistentCookieStore(Context context) {\n        cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);\n        cookies = new ConcurrentHashMap<>();\n        // Load any previously stored cookies into the store\n        Map<String, ?> cookiePrefsAll = cookiePrefs.getAll();\n        for (Map.Entry<String, ?> entry : cookiePrefsAll.entrySet()) {\n            if (entry.getValue() != null && !entry.getKey().startsWith(COOKIE_NAME_PREFIX)) {\n                String host = entry.getKey();\n                if (!cookies.containsKey(host)) {\n                    cookies.put(host, new ConcurrentHashMap<String, HttpCookie>());\n                }\n                String[] cookieNames = TextUtils.split((String) entry.getValue(), \",\");\n                for (String cookieName : cookieNames) {\n                    String encodedCookie = (String) cookiePrefsAll.get(COOKIE_NAME_PREFIX + cookieName);\n                    if (!TextUtils.isEmpty(encodedCookie)) {\n                        cookies.get(host).put(cookieName, decodeCookie(encodedCookie));\n                    }\n                }\n            }\n        }\n    }\n\n    @Override\n    public void add(URI uri, HttpCookie cookie) {\n        String name = getCookieToken(uri, cookie);\n        if (!cookie.hasExpired()) {\n            if (!cookies.containsKey(uri.getHost())) {\n                cookies.put(uri.getHost(), new ConcurrentHashMap<String, HttpCookie>());\n            }\n            cookies.get(uri.getHost()).put(name, cookie);\n        } else {\n            if (cookies.containsKey(uri.getHost())) {\n                cookies.get(uri.getHost()).remove(name);\n            }\n        }\n        cookiePrefs.edit()\n                .putString(uri.getHost(), TextUtils.join(\",\", cookies.keySet()))\n                .putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie)))\n                .apply();\n\n    }\n\n    @Override\n    public List<HttpCookie> get(URI uri) {\n        List<HttpCookie> httpCookies = new ArrayList<>(0);\n        if (cookies.containsKey(uri.getHost())) {\n            httpCookies.addAll(cookies.get(uri.getHost()).values());\n        }\n        return httpCookies;\n    }\n\n    @Override\n    public List<HttpCookie> getCookies() {\n        List<HttpCookie> httpCookies = new ArrayList<>(0);\n        for (String host : cookies.keySet()) {\n            httpCookies.addAll(cookies.get(host).values());\n        }\n        return httpCookies;\n    }\n\n    @Override\n    public List<URI> getURIs() {\n        List<URI> uris = new ArrayList<>(cookies.size());\n        for (String key : cookies.keySet()) {\n            try {\n                uris.add(new URI(key));\n            } catch (URISyntaxException e) {\n                e.printStackTrace();\n            }\n        }\n        return uris;\n    }\n\n    @Override\n    public boolean remove(URI uri, HttpCookie cookie) {\n        String name = getCookieToken(uri, cookie);\n        if (cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {\n            cookies.get(uri.getHost()).remove(name);\n            SharedPreferences.Editor editor = cookiePrefs.edit();\n            if (cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {\n                editor.remove(COOKIE_NAME_PREFIX + name);\n            }\n            editor.putString(uri.getHost(), TextUtils.join(\",\", cookies.keySet()));\n            editor.apply();\n            return true;\n\n        }\n        return false;\n    }\n\n    @Override\n    public boolean removeAll() {\n        cookies.clear();\n        return cookiePrefs.edit().clear().commit();\n    }\n\n    protected String getCookieToken(URI uri, HttpCookie cookie) {\n        return cookie.getName() + cookie.getDomain();\n    }\n\n\n    //\n    // Cookie serialization/deserialization\n    //\n\n    protected String encodeCookie(SerializableCookie cookie) {\n        ByteArrayOutputStream os = new ByteArrayOutputStream();\n        try {\n            ObjectOutputStream outputStream = new ObjectOutputStream(os);\n            outputStream.writeObject(cookie);\n        } catch (Exception e) {\n            return null;\n        }\n\n        return byteArrayToHexString(os.toByteArray());\n    }\n\n    protected HttpCookie decodeCookie(String cookieStr) {\n        byte[] bytes = hexStringToByteArray(cookieStr);\n        ByteArrayInputStream is = new ByteArrayInputStream(bytes);\n        HttpCookie cookie = null;\n        try {\n            ObjectInputStream ois = new ObjectInputStream(is);\n            cookie = ((SerializableCookie) ois.readObject()).getCookie();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return cookie;\n    }\n\n    // Using some super basic byte array <-> hex conversions so we don't have\n    // to rely on any large Base64 libraries. Can be overridden if you like!\n    protected String byteArrayToHexString(byte[] b) {\n        StringBuffer sb = new StringBuffer(b.length * 2);\n        for (byte element : b) {\n            int v = element & 0xff;\n            if (v < 16) {\n                sb.append('0');\n            }\n            sb.append(Integer.toHexString(v));\n        }\n        return sb.toString().toUpperCase();\n    }\n\n    protected byte[] hexStringToByteArray(String s) {\n        int len = s.length();\n        byte[] data = new byte[len / 2];\n        for (int i = 0; i < len; i += 2) {\n            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));\n        }\n        return data;\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/PrefUtils.java",
    "content": "package com.halzhang.android.startupnews.data.utils;\n\nimport android.content.Context;\nimport android.preference.PreferenceManager;\n\n/**\n * Created by Hal on 2016/5/30.\n */\npublic class PrefUtils {\n\n    public static void set(Context ctx, final String key, final String value) {\n        PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(key, value).apply();\n    }\n\n    public static String get(Context context, final String key) {\n        return PreferenceManager.getDefaultSharedPreferences(context).getString(key, null);\n    }\n}\n"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/SerializableCookie.java",
    "content": "/*\n    Android Asynchronous Http Client\n    Copyright (c) 2011 James Smith <james@loopj.com>\n    http://loopj.com\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n*/\n\npackage com.halzhang.android.startupnews.data.utils;\n\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\nimport java.net.HttpCookie;\n\n\n/**\n * A wrapper class around {@link HttpCookie} and/or {@link HttpCookie}\n * designed for use in {@link PersistentCookieStore}.\n */\npublic class SerializableCookie implements Serializable {\n    private static final long serialVersionUID = 6374381828722046732L;\n\n    private transient final HttpCookie cookie;\n    private transient HttpCookie clientCookie;\n\n    public SerializableCookie(HttpCookie cookie) {\n        this.cookie = cookie;\n    }\n\n    public HttpCookie getCookie() {\n        HttpCookie bestCookie = cookie;\n        if(clientCookie != null) {\n            bestCookie = clientCookie;\n        }\n        return bestCookie;\n    }\n\n    private void writeObject(ObjectOutputStream out) throws IOException {\n        out.writeObject(cookie.getName());\n        out.writeObject(cookie.getValue());\n        out.writeObject(cookie.getComment());\n        out.writeObject(cookie.getDomain());\n        out.writeLong(cookie.getMaxAge());\n        out.writeObject(cookie.getPath());\n        out.writeInt(cookie.getVersion());\n        out.writeBoolean(cookie.getSecure());\n    }\n\n    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {\n        String name = (String)in.readObject();\n        String value = (String)in.readObject();\n        clientCookie = new HttpCookie(name, value);\n        clientCookie.setComment((String) in.readObject());\n        clientCookie.setDomain((String) in.readObject());\n        clientCookie.setMaxAge(in.readLong());\n        clientCookie.setPath((String)in.readObject());\n        clientCookie.setVersion(in.readInt());\n        clientCookie.setSecure(in.readBoolean());\n    }\n}"
  },
  {
    "path": "data/src/main/java/com/halzhang/android/startupnews/data/utils/SessionManager.java",
    "content": "/*\n * Copyright (C) 2013 HalZhang.\n *\n * http://www.gnu.org/licenses/gpl-3.0.txt\n */\n\npackage com.halzhang.android.startupnews.data.utils;\n\nimport android.content.Context;\nimport android.text.TextUtils;\n\nimport com.halzhang.android.startupnews.data.R;\nimport com.halzhang.android.startupnews.data.entity.SNSession;\n\nimport javax.inject.Singleton;\n\n/**\n * StartupNews\n * <p>\n * Session管理\n * </p>\n *\n * @author <a href=\"http://weibo.com/halzhang\">Hal</a>\n * @version Apr 23, 2013\n */\n@Singleton\npublic class SessionManager {\n\n    private SNSession mSession;\n\n    private Context mContext;\n\n    public SessionManager(Context context) {\n        mSession = new SNSession();\n        initSession(context);\n    }\n\n    public void initSession(Context context) {\n        mContext = context;\n        initSessionFromPref();\n    }\n\n    public void storeSession(SNSession session) {\n        mSession = session;\n        saveSessionToPref();\n    }\n\n    public void storeSession(String user, String id) {\n        if (mSession == null) {\n            mSession = new SNSession(user, id);\n        } else {\n            mSession.setId(id);\n            mSession.setUser(user);\n        }\n        storeSession(mSession);\n    }\n\n    private void saveSessionToPref() {\n        if (mSession != null) {\n            StringBuilder builder = new StringBuilder();\n            builder.append(mSession.getId()).append(\";\").append(mSession.getUser());\n            PrefUtils.set(mContext, mContext.getString(R.string.pref_key_cookie),\n                    builder.toString());\n        }\n    }\n\n    private void initSessionFromPref() {\n        String cookie = PrefUtils.get(mContext, mContext.getString(R.string.pref_key_cookie));\n        if (!TextUtils.isEmpty(cookie)) {\n            String[] datas = cookie.split(\";\");\n            if (datas.length == 2) {\n                if (mSession == null) {\n                    mSession = new SNSession(datas[1], datas[0]);\n                } else {\n                    mSession.setId(datas[0]);\n                    mSession.setUser(datas[1]);\n                }\n            }\n        }\n    }\n\n    public void clear() {\n        PrefUtils.set(mContext, mContext.getString(R.string.pref_key_cookie), \"\");\n        mSession.clear();\n    }\n\n    public String getSessionId() {\n        if (mSession != null) {\n            return mSession.getId();\n        }\n        return null;\n    }\n\n    public String getSessionUser() {\n        if (mSession != null) {\n            return mSession.getUser();\n        }\n        return null;\n    }\n\n    /**\n     * session是否有效\n     *\n     * @return true 有效\n     */\n    public boolean isValid() {\n        return mSession != null && !TextUtils.isEmpty(mSession.getUser());\n    }\n\n    public String getCookieString() {\n        return \"user=\" + mSession.getUser();\n    }\n}\n"
  },
  {
    "path": "data/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">data</string>\n    <string name=\"pref_key_cookie\">pref_key_cookie</string>\n</resources>\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Thu Nov 26 14:56:12 CST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.10-all.zip\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':data',\n        ':3rdlib:commonlog',\n        ':3rdlib:commontoast',\n        ':app'"
  },
  {
    "path": "wait_for_emulator.sh",
    "content": "#!/bin/bash\n\n# Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain\n\nset +e\n\nbootanim=\"\"\nfailcounter=0\nuntil [[ \"$bootanim\" =~ \"stopped\" ]]; do\n   bootanim=`adb -e shell getprop init.svc.bootanim 2>&1`\n   echo \"$bootanim\"\n   if [[ \"$bootanim\" =~ \"not found\" ]]; then\n      let \"failcounter += 1\"\n      if [[ $failcounter -gt 15 ]]; then\n        echo \"Failed to start emulator\"\n        exit 1\n      fi\n   fi\n   sleep 1\ndone\necho \"Done\""
  }
]