init
56
.gitignore
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
Thumbs.db#built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
|
||||||
|
# files for the dex VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Windows thumbnail db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# OSX files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Eclipse project files
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
|
||||||
|
# Android Studio
|
||||||
|
.idea
|
||||||
|
.idea/workspace.xml
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
captures/
|
||||||
|
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# calabash
|
||||||
|
screenshot*
|
||||||
|
.irb-history
|
||||||
|
test_servers
|
||||||
|
|
||||||
|
# crashlytics
|
||||||
|
crashlytics-build.properties
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
|
||||||
|
gradle.properties
|
||||||
|
|
||||||
|
screenshot*
|
||||||
|
|
||||||
|
# sbt
|
||||||
|
00-gradle-generated.sbt
|
||||||
|
target/
|
||||||
|
project/project
|
||||||
|
|
||||||
|
|
||||||
34
XYZReader/build.gradle
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:1.1.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 22
|
||||||
|
buildToolsVersion "22.0.1"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile 'com.android.support:support-v4:22.0.0'
|
||||||
|
compile 'com.android.support:support-v13:22.0.0'
|
||||||
|
compile 'com.android.support:appcompat-v7:22.0.0'
|
||||||
|
compile 'com.android.support:palette-v7:22.0.0'
|
||||||
|
compile 'com.android.support:recyclerview-v7:22.0.0'
|
||||||
|
compile 'com.android.support:cardview-v7:22.0.0'
|
||||||
|
compile 'com.squareup.okhttp:okhttp:1.1.0'
|
||||||
|
compile files('libs/volley.jar')
|
||||||
|
}
|
||||||
1
XYZReader/build.sbt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
protifySettings
|
||||||
BIN
XYZReader/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
XYZReader/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Wed Apr 10 15:27:10 PDT 2013
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||||
164
XYZReader/gradlew
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >&-
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >&-
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
90
XYZReader/gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
BIN
XYZReader/libs/volley.jar
Normal file
48
XYZReader/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.xyzreader"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.Bacon">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.ArticleListActivity"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.ArticleDetailActivity"
|
||||||
|
android:parentActivityName=".ui.ArticleListActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.com.example.xyzreader.items" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".ui.ArticleListActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service android:name=".data.UpdaterService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider android:name=".data.ItemsProvider"
|
||||||
|
android:authorities="com.example.xyzreader"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
BIN
XYZReader/src/main/assets/Rosario-Regular.ttf
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package com.example.xyzreader.data;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.CursorLoader;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for loading a list of articles or a single article.
|
||||||
|
*/
|
||||||
|
public class ArticleLoader extends CursorLoader {
|
||||||
|
public static ArticleLoader newAllArticlesInstance(Context context) {
|
||||||
|
return new ArticleLoader(context, ItemsContract.Items.buildDirUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArticleLoader newInstanceForItemId(Context context, long itemId) {
|
||||||
|
return new ArticleLoader(context, ItemsContract.Items.buildItemUri(itemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArticleLoader(Context context, Uri uri) {
|
||||||
|
super(context, uri, Query.PROJECTION, null, null, ItemsContract.Items.DEFAULT_SORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Query {
|
||||||
|
String[] PROJECTION = {
|
||||||
|
ItemsContract.Items._ID,
|
||||||
|
ItemsContract.Items.TITLE,
|
||||||
|
ItemsContract.Items.PUBLISHED_DATE,
|
||||||
|
ItemsContract.Items.AUTHOR,
|
||||||
|
ItemsContract.Items.THUMB_URL,
|
||||||
|
ItemsContract.Items.PHOTO_URL,
|
||||||
|
ItemsContract.Items.ASPECT_RATIO,
|
||||||
|
ItemsContract.Items.BODY,
|
||||||
|
};
|
||||||
|
|
||||||
|
int _ID = 0;
|
||||||
|
int TITLE = 1;
|
||||||
|
int PUBLISHED_DATE = 2;
|
||||||
|
int AUTHOR = 3;
|
||||||
|
int THUMB_URL = 4;
|
||||||
|
int PHOTO_URL = 5;
|
||||||
|
int ASPECT_RATIO = 6;
|
||||||
|
int BODY = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.example.xyzreader.data;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
public class ItemsContract {
|
||||||
|
public static final String CONTENT_AUTHORITY = "com.example.xyzreader";
|
||||||
|
public static final Uri BASE_URI = Uri.parse("content://com.example.xyzreader");
|
||||||
|
|
||||||
|
interface ItemsColumns {
|
||||||
|
/** Type: INTEGER PRIMARY KEY AUTOINCREMENT */
|
||||||
|
String _ID = "_id";
|
||||||
|
/** Type: TEXT */
|
||||||
|
String SERVER_ID = "server_id";
|
||||||
|
/** Type: TEXT NOT NULL */
|
||||||
|
String TITLE = "title";
|
||||||
|
/** Type: TEXT NOT NULL */
|
||||||
|
String AUTHOR = "author";
|
||||||
|
/** Type: TEXT NOT NULL */
|
||||||
|
String BODY = "body";
|
||||||
|
/** Type: TEXT NOT NULL */
|
||||||
|
String THUMB_URL = "thumb_url";
|
||||||
|
/** Type: TEXT NOT NULL */
|
||||||
|
String PHOTO_URL = "photo_url";
|
||||||
|
/** Type: REAL NOT NULL DEFAULT 1.5 */
|
||||||
|
String ASPECT_RATIO = "aspect_ratio";
|
||||||
|
/** Type: INTEGER NOT NULL DEFAULT 0 */
|
||||||
|
String PUBLISHED_DATE = "published_date";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Items implements ItemsColumns {
|
||||||
|
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.example.xyzreader.items";
|
||||||
|
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.example.xyzreader.items";
|
||||||
|
|
||||||
|
public static final String DEFAULT_SORT = PUBLISHED_DATE + " DESC";
|
||||||
|
|
||||||
|
/** Matches: /items/ */
|
||||||
|
public static Uri buildDirUri() {
|
||||||
|
return BASE_URI.buildUpon().appendPath("items").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Matches: /items/[_id]/ */
|
||||||
|
public static Uri buildItemUri(long _id) {
|
||||||
|
return BASE_URI.buildUpon().appendPath("items").appendPath(Long.toString(_id)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read item ID item detail URI. */
|
||||||
|
public static long getItemId(Uri itemUri) {
|
||||||
|
return Long.parseLong(itemUri.getPathSegments().get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemsContract() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.example.xyzreader.data;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import static com.example.xyzreader.data.ItemsProvider.Tables;
|
||||||
|
|
||||||
|
public class ItemsDatabase extends SQLiteOpenHelper {
|
||||||
|
private static final String DATABASE_NAME = "xyzreader.db";
|
||||||
|
private static final int DATABASE_VERSION = 1;
|
||||||
|
|
||||||
|
public ItemsDatabase(Context context) {
|
||||||
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL("CREATE TABLE " + Tables.ITEMS + " ("
|
||||||
|
+ ItemsContract.ItemsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
+ ItemsContract.ItemsColumns.SERVER_ID + " TEXT,"
|
||||||
|
+ ItemsContract.ItemsColumns.TITLE + " TEXT NOT NULL,"
|
||||||
|
+ ItemsContract.ItemsColumns.AUTHOR + " TEXT NOT NULL,"
|
||||||
|
+ ItemsContract.ItemsColumns.BODY + " TEXT NOT NULL,"
|
||||||
|
+ ItemsContract.ItemsColumns.THUMB_URL + " TEXT NOT NULL,"
|
||||||
|
+ ItemsContract.ItemsColumns.PHOTO_URL + " TEXT NOT NULL,"
|
||||||
|
+ ItemsContract.ItemsColumns.ASPECT_RATIO + " REAL NOT NULL DEFAULT 1.5,"
|
||||||
|
+ ItemsContract.ItemsColumns.PUBLISHED_DATE + " INTEGER NOT NULL DEFAULT 0"
|
||||||
|
+ ")" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + Tables.ITEMS);
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
|
||||||
|
package com.example.xyzreader.data;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentProviderOperation;
|
||||||
|
import android.content.ContentProviderResult;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.OperationApplicationException;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ItemsProvider extends ContentProvider {
|
||||||
|
private SQLiteOpenHelper mOpenHelper;
|
||||||
|
|
||||||
|
interface Tables {
|
||||||
|
String ITEMS = "items";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int ITEMS = 0;
|
||||||
|
private static final int ITEMS__ID = 1;
|
||||||
|
|
||||||
|
private static final UriMatcher sUriMatcher = buildUriMatcher();
|
||||||
|
|
||||||
|
private static UriMatcher buildUriMatcher() {
|
||||||
|
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
final String authority = ItemsContract.CONTENT_AUTHORITY;
|
||||||
|
matcher.addURI(authority, "items", ITEMS);
|
||||||
|
matcher.addURI(authority, "items/#", ITEMS__ID);
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
mOpenHelper = new ItemsDatabase(getContext());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(Uri uri) {
|
||||||
|
final int match = sUriMatcher.match(uri);
|
||||||
|
switch (match) {
|
||||||
|
case ITEMS:
|
||||||
|
return ItemsContract.Items.CONTENT_TYPE;
|
||||||
|
case ITEMS__ID:
|
||||||
|
return ItemsContract.Items.CONTENT_ITEM_TYPE;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
|
final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||||
|
final SelectionBuilder builder = buildSelection(uri);
|
||||||
|
Cursor cursor = builder.where(selection, selectionArgs).query(db, projection, sortOrder);
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
|
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||||
|
final int match = sUriMatcher.match(uri);
|
||||||
|
switch (match) {
|
||||||
|
case ITEMS: {
|
||||||
|
final long _id = db.insertOrThrow(Tables.ITEMS, null, values);
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return ItemsContract.Items.buildItemUri(_id);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
|
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||||
|
final SelectionBuilder builder = buildSelection(uri);
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return builder.where(selection, selectionArgs).update(db, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||||
|
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||||
|
final SelectionBuilder builder = buildSelection(uri);
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
|
return builder.where(selection, selectionArgs).delete(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SelectionBuilder buildSelection(Uri uri) {
|
||||||
|
final SelectionBuilder builder = new SelectionBuilder();
|
||||||
|
final int match = sUriMatcher.match(uri);
|
||||||
|
return buildSelection(uri, match, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SelectionBuilder buildSelection(Uri uri, int match, SelectionBuilder builder) {
|
||||||
|
final List<String> paths = uri.getPathSegments();
|
||||||
|
switch (match) {
|
||||||
|
case ITEMS: {
|
||||||
|
return builder.table(Tables.ITEMS);
|
||||||
|
}
|
||||||
|
case ITEMS__ID: {
|
||||||
|
final String _id = paths.get(1);
|
||||||
|
return builder.table(Tables.ITEMS).where(ItemsContract.Items._ID + "=?", _id);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given set of {@link ContentProviderOperation}, executing inside
|
||||||
|
* a {@link SQLiteDatabase} transaction. All changes will be rolled back if
|
||||||
|
* any single one fails.
|
||||||
|
*/
|
||||||
|
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
|
||||||
|
throws OperationApplicationException {
|
||||||
|
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||||
|
db.beginTransaction();
|
||||||
|
try {
|
||||||
|
final int numOperations = operations.size();
|
||||||
|
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
|
||||||
|
for (int i = 0; i < numOperations; i++) {
|
||||||
|
results[i] = operations.get(i).apply(this, results, i);
|
||||||
|
}
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
return results;
|
||||||
|
} finally {
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modifications:
|
||||||
|
* -Imported from AOSP frameworks/base/core/java/com/android/internal/content
|
||||||
|
* -Changed package name
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.xyzreader.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for building selection clauses for {@link SQLiteDatabase}. Each
|
||||||
|
* appended clause is combined using {@code AND}. This class is <em>not</em>
|
||||||
|
* thread safe.
|
||||||
|
*/
|
||||||
|
public class SelectionBuilder {
|
||||||
|
private String mTable = null;
|
||||||
|
private HashMap<String, String> mProjectionMap;
|
||||||
|
private StringBuilder mSelection;
|
||||||
|
private ArrayList<String> mSelectionArgs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset any internal state, allowing this builder to be recycled.
|
||||||
|
*/
|
||||||
|
public SelectionBuilder reset() {
|
||||||
|
mTable = null;
|
||||||
|
if (mProjectionMap != null) {
|
||||||
|
mProjectionMap.clear();
|
||||||
|
}
|
||||||
|
if (mSelection != null) {
|
||||||
|
mSelection.setLength(0);
|
||||||
|
}
|
||||||
|
if (mSelectionArgs != null) {
|
||||||
|
mSelectionArgs.clear();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append the given selection clause to the internal state. Each clause is
|
||||||
|
* surrounded with parenthesis and combined using {@code AND}.
|
||||||
|
*/
|
||||||
|
public SelectionBuilder where(String selection, String... selectionArgs) {
|
||||||
|
if (TextUtils.isEmpty(selection)) {
|
||||||
|
if (selectionArgs != null && selectionArgs.length > 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Valid selection required when including arguments=");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shortcut when clause is empty
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureSelection(selection.length());
|
||||||
|
if (mSelection.length() > 0) {
|
||||||
|
mSelection.append(" AND ");
|
||||||
|
}
|
||||||
|
|
||||||
|
mSelection.append("(").append(selection).append(")");
|
||||||
|
if (selectionArgs != null) {
|
||||||
|
ensureSelectionArgs();
|
||||||
|
for (String arg : selectionArgs) {
|
||||||
|
mSelectionArgs.add(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionBuilder table(String table) {
|
||||||
|
mTable = table;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTable() {
|
||||||
|
if (mTable == null) {
|
||||||
|
throw new IllegalStateException("Table not specified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureProjectionMap() {
|
||||||
|
if (mProjectionMap == null) {
|
||||||
|
mProjectionMap = new HashMap<String, String>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureSelection(int lengthHint) {
|
||||||
|
if (mSelection == null) {
|
||||||
|
mSelection = new StringBuilder(lengthHint + 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureSelectionArgs() {
|
||||||
|
if (mSelectionArgs == null) {
|
||||||
|
mSelectionArgs = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionBuilder mapToTable(String column, String table) {
|
||||||
|
ensureProjectionMap();
|
||||||
|
mProjectionMap.put(column, table + "." + column);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionBuilder map(String fromColumn, String toClause) {
|
||||||
|
ensureProjectionMap();
|
||||||
|
mProjectionMap.put(fromColumn, toClause + " AS " + fromColumn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return selection string for current internal state.
|
||||||
|
*
|
||||||
|
* @see #getSelectionArgs()
|
||||||
|
*/
|
||||||
|
public String getSelection() {
|
||||||
|
if (mSelection != null) {
|
||||||
|
return mSelection.toString();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return selection arguments for current internal state.
|
||||||
|
*
|
||||||
|
* @see #getSelection()
|
||||||
|
*/
|
||||||
|
public String[] getSelectionArgs() {
|
||||||
|
if (mSelectionArgs != null) {
|
||||||
|
return mSelectionArgs.toArray(new String[mSelectionArgs.size()]);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mapColumns(String[] columns) {
|
||||||
|
if (mProjectionMap == null) return;
|
||||||
|
for (int i = 0; i < columns.length; i++) {
|
||||||
|
final String target = mProjectionMap.get(columns[i]);
|
||||||
|
if (target != null) {
|
||||||
|
columns[i] = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SelectionBuilder[table=" + mTable + ", selection=" + getSelection()
|
||||||
|
+ ", selectionArgs=" + Arrays.toString(getSelectionArgs()) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute query using the current internal state as {@code WHERE} clause.
|
||||||
|
*/
|
||||||
|
public Cursor query(SQLiteDatabase db, String[] columns, String orderBy) {
|
||||||
|
return query(db, columns, null, null, orderBy, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute query using the current internal state as {@code WHERE} clause.
|
||||||
|
*/
|
||||||
|
public Cursor query(SQLiteDatabase db, String[] columns, String groupBy,
|
||||||
|
String having, String orderBy, String limit) {
|
||||||
|
assertTable();
|
||||||
|
if (columns != null) mapColumns(columns);
|
||||||
|
return db.query(mTable, columns, getSelection(), getSelectionArgs(), groupBy, having,
|
||||||
|
orderBy, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute update using the current internal state as {@code WHERE} clause.
|
||||||
|
*/
|
||||||
|
public int update(SQLiteDatabase db, ContentValues values) {
|
||||||
|
assertTable();
|
||||||
|
return db.update(mTable, values, getSelection(), getSelectionArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute delete using the current internal state as {@code WHERE} clause.
|
||||||
|
*/
|
||||||
|
public int delete(SQLiteDatabase db) {
|
||||||
|
assertTable();
|
||||||
|
return db.delete(mTable, getSelection(), getSelectionArgs());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.example.xyzreader.data;
|
||||||
|
|
||||||
|
import android.app.IntentService;
|
||||||
|
import android.content.ContentProviderOperation;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.OperationApplicationException;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.text.format.Time;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.example.xyzreader.remote.RemoteEndpointUtil;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class UpdaterService extends IntentService {
|
||||||
|
private static final String TAG = "UpdaterService";
|
||||||
|
|
||||||
|
public static final String BROADCAST_ACTION_STATE_CHANGE
|
||||||
|
= "com.example.xyzreader.intent.action.STATE_CHANGE";
|
||||||
|
public static final String EXTRA_REFRESHING
|
||||||
|
= "com.example.xyzreader.intent.extra.REFRESHING";
|
||||||
|
|
||||||
|
public UpdaterService() {
|
||||||
|
super(TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent) {
|
||||||
|
Time time = new Time();
|
||||||
|
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo ni = cm.getActiveNetworkInfo();
|
||||||
|
if (ni == null || !ni.isConnected()) {
|
||||||
|
Log.w(TAG, "Not online, not refreshing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStickyBroadcast(
|
||||||
|
new Intent(BROADCAST_ACTION_STATE_CHANGE).putExtra(EXTRA_REFRESHING, true));
|
||||||
|
|
||||||
|
// Don't even inspect the intent, we only do one thing, and that's fetch content.
|
||||||
|
ArrayList<ContentProviderOperation> cpo = new ArrayList<ContentProviderOperation>();
|
||||||
|
|
||||||
|
Uri dirUri = ItemsContract.Items.buildDirUri();
|
||||||
|
|
||||||
|
// Delete all items
|
||||||
|
cpo.add(ContentProviderOperation.newDelete(dirUri).build());
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONArray array = RemoteEndpointUtil.fetchJsonArray();
|
||||||
|
if (array == null) {
|
||||||
|
throw new JSONException("Invalid parsed item array" );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
JSONObject object = array.getJSONObject(i);
|
||||||
|
values.put(ItemsContract.Items.SERVER_ID, object.getString("id" ));
|
||||||
|
values.put(ItemsContract.Items.AUTHOR, object.getString("author" ));
|
||||||
|
values.put(ItemsContract.Items.TITLE, object.getString("title" ));
|
||||||
|
values.put(ItemsContract.Items.BODY, object.getString("body" ));
|
||||||
|
values.put(ItemsContract.Items.THUMB_URL, object.getString("thumb" ));
|
||||||
|
values.put(ItemsContract.Items.PHOTO_URL, object.getString("photo" ));
|
||||||
|
values.put(ItemsContract.Items.ASPECT_RATIO, object.getString("aspect_ratio" ));
|
||||||
|
time.parse3339(object.getString("published_date"));
|
||||||
|
values.put(ItemsContract.Items.PUBLISHED_DATE, time.toMillis(false));
|
||||||
|
cpo.add(ContentProviderOperation.newInsert(dirUri).withValues(values).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
getContentResolver().applyBatch(ItemsContract.CONTENT_AUTHORITY, cpo);
|
||||||
|
|
||||||
|
} catch (JSONException | RemoteException | OperationApplicationException e) {
|
||||||
|
Log.e(TAG, "Error updating content.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStickyBroadcast(
|
||||||
|
new Intent(BROADCAST_ACTION_STATE_CHANGE).putExtra(EXTRA_REFRESHING, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.example.xyzreader.remote;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class Config {
|
||||||
|
public static final URL BASE_URL;
|
||||||
|
|
||||||
|
static {
|
||||||
|
URL url = null;
|
||||||
|
try {
|
||||||
|
url = new URL("https://dl.dropboxusercontent.com/u/231329/xyzreader_data/data.json" );
|
||||||
|
} catch (MalformedURLException ignored) {
|
||||||
|
// TODO: throw a real error
|
||||||
|
}
|
||||||
|
|
||||||
|
BASE_URL = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.example.xyzreader.remote;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.squareup.okhttp.OkHttpClient;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class RemoteEndpointUtil {
|
||||||
|
private static final String TAG = "RemoteEndpointUtil";
|
||||||
|
|
||||||
|
private RemoteEndpointUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONArray fetchJsonArray() {
|
||||||
|
String itemsJson = null;
|
||||||
|
try {
|
||||||
|
itemsJson = fetchPlainText(Config.BASE_URL);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error fetching items JSON", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON
|
||||||
|
try {
|
||||||
|
JSONTokener tokener = new JSONTokener(itemsJson);
|
||||||
|
Object val = tokener.nextValue();
|
||||||
|
if (!(val instanceof JSONArray)) {
|
||||||
|
throw new JSONException("Expected JSONArray");
|
||||||
|
}
|
||||||
|
return (JSONArray) val;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "Error parsing items JSON", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String fetchPlainText(URL url) throws IOException {
|
||||||
|
return new String(fetch(url), "UTF-8" );
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] fetch(URL url) throws IOException {
|
||||||
|
InputStream in = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
OkHttpClient client = new OkHttpClient();
|
||||||
|
HttpURLConnection conn = client.open(url);
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
in = conn.getInputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = in.read(buffer)) > 0) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (in != null) {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.app.FragmentManager;
|
||||||
|
import android.app.LoaderManager;
|
||||||
|
import android.content.Loader;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v13.app.FragmentStatePagerAdapter;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.support.v7.app.ActionBarActivity;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
|
||||||
|
import com.example.xyzreader.R;
|
||||||
|
import com.example.xyzreader.data.ArticleLoader;
|
||||||
|
import com.example.xyzreader.data.ItemsContract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An activity representing a single Article detail screen, letting you swipe between articles.
|
||||||
|
*/
|
||||||
|
public class ArticleDetailActivity extends ActionBarActivity
|
||||||
|
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
|
private Cursor mCursor;
|
||||||
|
private long mStartId;
|
||||||
|
|
||||||
|
private long mSelectedItemId;
|
||||||
|
private int mSelectedItemUpButtonFloor = Integer.MAX_VALUE;
|
||||||
|
private int mTopInset;
|
||||||
|
|
||||||
|
private ViewPager mPager;
|
||||||
|
private MyPagerAdapter mPagerAdapter;
|
||||||
|
private View mUpButtonContainer;
|
||||||
|
private View mUpButton;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||||
|
}
|
||||||
|
setContentView(R.layout.activity_article_detail);
|
||||||
|
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
|
||||||
|
mPagerAdapter = new MyPagerAdapter(getFragmentManager());
|
||||||
|
mPager = (ViewPager) findViewById(R.id.pager);
|
||||||
|
mPager.setAdapter(mPagerAdapter);
|
||||||
|
mPager.setPageMargin((int) TypedValue
|
||||||
|
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()));
|
||||||
|
mPager.setPageMarginDrawable(new ColorDrawable(0x22000000));
|
||||||
|
|
||||||
|
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPageScrollStateChanged(int state) {
|
||||||
|
super.onPageScrollStateChanged(state);
|
||||||
|
mUpButton.animate()
|
||||||
|
.alpha((state == ViewPager.SCROLL_STATE_IDLE) ? 1f : 0f)
|
||||||
|
.setDuration(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
if (mCursor != null) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
}
|
||||||
|
mSelectedItemId = mCursor.getLong(ArticleLoader.Query._ID);
|
||||||
|
updateUpButtonPosition();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mUpButtonContainer = findViewById(R.id.up_container);
|
||||||
|
|
||||||
|
mUpButton = findViewById(R.id.action_up);
|
||||||
|
mUpButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
onSupportNavigateUp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
mUpButtonContainer.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
|
||||||
|
@Override
|
||||||
|
public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
|
||||||
|
view.onApplyWindowInsets(windowInsets);
|
||||||
|
mTopInset = windowInsets.getSystemWindowInsetTop();
|
||||||
|
mUpButtonContainer.setTranslationY(mTopInset);
|
||||||
|
updateUpButtonPosition();
|
||||||
|
return windowInsets;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
if (getIntent() != null && getIntent().getData() != null) {
|
||||||
|
mStartId = ItemsContract.Items.getItemId(getIntent().getData());
|
||||||
|
mSelectedItemId = mStartId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
|
return ArticleLoader.newAllArticlesInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||||
|
mCursor = cursor;
|
||||||
|
mPagerAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
// Select the start ID
|
||||||
|
if (mStartId > 0) {
|
||||||
|
mCursor.moveToFirst();
|
||||||
|
// TODO: optimize
|
||||||
|
while (!mCursor.isAfterLast()) {
|
||||||
|
if (mCursor.getLong(ArticleLoader.Query._ID) == mStartId) {
|
||||||
|
final int position = mCursor.getPosition();
|
||||||
|
mPager.setCurrentItem(position, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mCursor.moveToNext();
|
||||||
|
}
|
||||||
|
mStartId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||||
|
mCursor = null;
|
||||||
|
mPagerAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUpButtonFloorChanged(long itemId, ArticleDetailFragment fragment) {
|
||||||
|
if (itemId == mSelectedItemId) {
|
||||||
|
mSelectedItemUpButtonFloor = fragment.getUpButtonFloor();
|
||||||
|
updateUpButtonPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUpButtonPosition() {
|
||||||
|
int upButtonNormalBottom = mTopInset + mUpButton.getHeight();
|
||||||
|
mUpButton.setTranslationY(Math.min(mSelectedItemUpButtonFloor - upButtonNormalBottom, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyPagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
public MyPagerAdapter(FragmentManager fm) {
|
||||||
|
super(fm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||||
|
super.setPrimaryItem(container, position, object);
|
||||||
|
ArticleDetailFragment fragment = (ArticleDetailFragment) object;
|
||||||
|
if (fragment != null) {
|
||||||
|
mSelectedItemUpButtonFloor = fragment.getUpButtonFloor();
|
||||||
|
updateUpButtonPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
return ArticleDetailFragment.newInstance(mCursor.getLong(ArticleLoader.Query._ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return (mCursor != null) ? mCursor.getCount() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.app.LoaderManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Loader;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.ShareCompat;
|
||||||
|
import android.support.v7.graphics.Palette;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.volley.VolleyError;
|
||||||
|
import com.android.volley.toolbox.ImageLoader;
|
||||||
|
import com.example.xyzreader.R;
|
||||||
|
import com.example.xyzreader.data.ArticleLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment representing a single Article detail screen. This fragment is
|
||||||
|
* either contained in a {@link ArticleListActivity} in two-pane mode (on
|
||||||
|
* tablets) or a {@link ArticleDetailActivity} on handsets.
|
||||||
|
*/
|
||||||
|
public class ArticleDetailFragment extends Fragment implements
|
||||||
|
LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
private static final String TAG = "ArticleDetailFragment";
|
||||||
|
|
||||||
|
public static final String ARG_ITEM_ID = "item_id";
|
||||||
|
private static final float PARALLAX_FACTOR = 1.25f;
|
||||||
|
|
||||||
|
private Cursor mCursor;
|
||||||
|
private long mItemId;
|
||||||
|
private View mRootView;
|
||||||
|
private int mMutedColor = 0xFF333333;
|
||||||
|
private ObservableScrollView mScrollView;
|
||||||
|
private DrawInsetsFrameLayout mDrawInsetsFrameLayout;
|
||||||
|
private ColorDrawable mStatusBarColorDrawable;
|
||||||
|
|
||||||
|
private int mTopInset;
|
||||||
|
private View mPhotoContainerView;
|
||||||
|
private ImageView mPhotoView;
|
||||||
|
private int mScrollY;
|
||||||
|
private boolean mIsCard = false;
|
||||||
|
private int mStatusBarFullOpacityBottom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||||
|
* fragment (e.g. upon screen orientation changes).
|
||||||
|
*/
|
||||||
|
public ArticleDetailFragment() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArticleDetailFragment newInstance(long itemId) {
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
arguments.putLong(ARG_ITEM_ID, itemId);
|
||||||
|
ArticleDetailFragment fragment = new ArticleDetailFragment();
|
||||||
|
fragment.setArguments(arguments);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (getArguments().containsKey(ARG_ITEM_ID)) {
|
||||||
|
mItemId = getArguments().getLong(ARG_ITEM_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
mIsCard = getResources().getBoolean(R.bool.detail_is_card);
|
||||||
|
mStatusBarFullOpacityBottom = getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.detail_card_top_margin);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArticleDetailActivity getActivityCast() {
|
||||||
|
return (ArticleDetailActivity) getActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
// In support library r8, calling initLoader for a fragment in a FragmentPagerAdapter in
|
||||||
|
// the fragment's onCreate may cause the same LoaderManager to be dealt to multiple
|
||||||
|
// fragments because their mIndex is -1 (haven't been added to the activity yet). Thus,
|
||||||
|
// we do this in onActivityCreated.
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
mRootView = inflater.inflate(R.layout.fragment_article_detail, container, false);
|
||||||
|
mDrawInsetsFrameLayout = (DrawInsetsFrameLayout)
|
||||||
|
mRootView.findViewById(R.id.draw_insets_frame_layout);
|
||||||
|
mDrawInsetsFrameLayout.setOnInsetsCallback(new DrawInsetsFrameLayout.OnInsetsCallback() {
|
||||||
|
@Override
|
||||||
|
public void onInsetsChanged(Rect insets) {
|
||||||
|
mTopInset = insets.top;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mScrollView = (ObservableScrollView) mRootView.findViewById(R.id.scrollview);
|
||||||
|
mScrollView.setCallbacks(new ObservableScrollView.Callbacks() {
|
||||||
|
@Override
|
||||||
|
public void onScrollChanged() {
|
||||||
|
mScrollY = mScrollView.getScrollY();
|
||||||
|
getActivityCast().onUpButtonFloorChanged(mItemId, ArticleDetailFragment.this);
|
||||||
|
mPhotoContainerView.setTranslationY((int) (mScrollY - mScrollY / PARALLAX_FACTOR));
|
||||||
|
updateStatusBar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mPhotoView = (ImageView) mRootView.findViewById(R.id.photo);
|
||||||
|
mPhotoContainerView = mRootView.findViewById(R.id.photo_container);
|
||||||
|
|
||||||
|
mStatusBarColorDrawable = new ColorDrawable(0);
|
||||||
|
|
||||||
|
mRootView.findViewById(R.id.share_fab).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
startActivity(Intent.createChooser(ShareCompat.IntentBuilder.from(getActivity())
|
||||||
|
.setType("text/plain")
|
||||||
|
.setText("Some sample text")
|
||||||
|
.getIntent(), getString(R.string.action_share)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bindViews();
|
||||||
|
updateStatusBar();
|
||||||
|
return mRootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatusBar() {
|
||||||
|
int color = 0;
|
||||||
|
if (mPhotoView != null && mTopInset != 0 && mScrollY > 0) {
|
||||||
|
float f = progress(mScrollY,
|
||||||
|
mStatusBarFullOpacityBottom - mTopInset * 3,
|
||||||
|
mStatusBarFullOpacityBottom - mTopInset);
|
||||||
|
color = Color.argb((int) (255 * f),
|
||||||
|
(int) (Color.red(mMutedColor) * 0.9),
|
||||||
|
(int) (Color.green(mMutedColor) * 0.9),
|
||||||
|
(int) (Color.blue(mMutedColor) * 0.9));
|
||||||
|
}
|
||||||
|
mStatusBarColorDrawable.setColor(color);
|
||||||
|
mDrawInsetsFrameLayout.setInsetBackground(mStatusBarColorDrawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float progress(float v, float min, float max) {
|
||||||
|
return constrain((v - min) / (max - min), 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float constrain(float val, float min, float max) {
|
||||||
|
if (val < min) {
|
||||||
|
return min;
|
||||||
|
} else if (val > max) {
|
||||||
|
return max;
|
||||||
|
} else {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindViews() {
|
||||||
|
if (mRootView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView titleView = (TextView) mRootView.findViewById(R.id.article_title);
|
||||||
|
TextView bylineView = (TextView) mRootView.findViewById(R.id.article_byline);
|
||||||
|
bylineView.setMovementMethod(new LinkMovementMethod());
|
||||||
|
TextView bodyView = (TextView) mRootView.findViewById(R.id.article_body);
|
||||||
|
bodyView.setTypeface(Typeface.createFromAsset(getResources().getAssets(), "Rosario-Regular.ttf"));
|
||||||
|
|
||||||
|
if (mCursor != null) {
|
||||||
|
mRootView.setAlpha(0);
|
||||||
|
mRootView.setVisibility(View.VISIBLE);
|
||||||
|
mRootView.animate().alpha(1);
|
||||||
|
titleView.setText(mCursor.getString(ArticleLoader.Query.TITLE));
|
||||||
|
bylineView.setText(Html.fromHtml(
|
||||||
|
DateUtils.getRelativeTimeSpanString(
|
||||||
|
mCursor.getLong(ArticleLoader.Query.PUBLISHED_DATE),
|
||||||
|
System.currentTimeMillis(), DateUtils.HOUR_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_ABBREV_ALL).toString()
|
||||||
|
+ " by <font color='#ffffff'>"
|
||||||
|
+ mCursor.getString(ArticleLoader.Query.AUTHOR)
|
||||||
|
+ "</font>"));
|
||||||
|
bodyView.setText(Html.fromHtml(mCursor.getString(ArticleLoader.Query.BODY)));
|
||||||
|
ImageLoaderHelper.getInstance(getActivity()).getImageLoader()
|
||||||
|
.get(mCursor.getString(ArticleLoader.Query.PHOTO_URL), new ImageLoader.ImageListener() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) {
|
||||||
|
Bitmap bitmap = imageContainer.getBitmap();
|
||||||
|
if (bitmap != null) {
|
||||||
|
Palette p = Palette.generate(bitmap, 12);
|
||||||
|
mMutedColor = p.getDarkMutedColor(0xFF333333);
|
||||||
|
mPhotoView.setImageBitmap(imageContainer.getBitmap());
|
||||||
|
mRootView.findViewById(R.id.meta_bar)
|
||||||
|
.setBackgroundColor(mMutedColor);
|
||||||
|
updateStatusBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError volleyError) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mRootView.setVisibility(View.GONE);
|
||||||
|
titleView.setText("N/A");
|
||||||
|
bylineView.setText("N/A" );
|
||||||
|
bodyView.setText("N/A");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
|
return ArticleLoader.newInstanceForItemId(getActivity(), mItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||||
|
if (!isAdded()) {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCursor = cursor;
|
||||||
|
if (mCursor != null && !mCursor.moveToFirst()) {
|
||||||
|
Log.e(TAG, "Error reading item detail cursor");
|
||||||
|
mCursor.close();
|
||||||
|
mCursor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||||
|
mCursor = null;
|
||||||
|
bindViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUpButtonFloor() {
|
||||||
|
if (mPhotoContainerView == null || mPhotoView.getHeight() == 0) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// account for parallax
|
||||||
|
return mIsCard
|
||||||
|
? (int) mPhotoContainerView.getTranslationY() + mPhotoView.getHeight() - mScrollY
|
||||||
|
: mPhotoView.getHeight() - mScrollY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.app.LoaderManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.Loader;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.app.ActionBarActivity;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.StaggeredGridLayoutManager;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.example.xyzreader.R;
|
||||||
|
import com.example.xyzreader.data.ArticleLoader;
|
||||||
|
import com.example.xyzreader.data.ItemsContract;
|
||||||
|
import com.example.xyzreader.data.UpdaterService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An activity representing a list of Articles. This activity has different presentations for
|
||||||
|
* handset and tablet-size devices. On handsets, the activity presents a list of items, which when
|
||||||
|
* touched, lead to a {@link ArticleDetailActivity} representing item details. On tablets, the
|
||||||
|
* activity presents a grid of items as cards.
|
||||||
|
*/
|
||||||
|
public class ArticleListActivity extends ActionBarActivity implements
|
||||||
|
LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
|
private Toolbar mToolbar;
|
||||||
|
private SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
private RecyclerView mRecyclerView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_article_list);
|
||||||
|
|
||||||
|
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
|
||||||
|
final View toolbarContainerView = findViewById(R.id.toolbar_container);
|
||||||
|
|
||||||
|
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
|
||||||
|
|
||||||
|
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
startService(new Intent(this, UpdaterService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
registerReceiver(mRefreshingReceiver,
|
||||||
|
new IntentFilter(UpdaterService.BROADCAST_ACTION_STATE_CHANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
unregisterReceiver(mRefreshingReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean mIsRefreshing = false;
|
||||||
|
|
||||||
|
private BroadcastReceiver mRefreshingReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (UpdaterService.BROADCAST_ACTION_STATE_CHANGE.equals(intent.getAction())) {
|
||||||
|
mIsRefreshing = intent.getBooleanExtra(UpdaterService.EXTRA_REFRESHING, false);
|
||||||
|
updateRefreshingUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void updateRefreshingUI() {
|
||||||
|
mSwipeRefreshLayout.setRefreshing(mIsRefreshing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
|
return ArticleLoader.newAllArticlesInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||||
|
Adapter adapter = new Adapter(cursor);
|
||||||
|
adapter.setHasStableIds(true);
|
||||||
|
mRecyclerView.setAdapter(adapter);
|
||||||
|
int columnCount = getResources().getInteger(R.integer.list_column_count);
|
||||||
|
StaggeredGridLayoutManager sglm =
|
||||||
|
new StaggeredGridLayoutManager(columnCount, StaggeredGridLayoutManager.VERTICAL);
|
||||||
|
mRecyclerView.setLayoutManager(sglm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
mRecyclerView.setAdapter(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Adapter extends RecyclerView.Adapter<ViewHolder> {
|
||||||
|
private Cursor mCursor;
|
||||||
|
|
||||||
|
public Adapter(Cursor cursor) {
|
||||||
|
mCursor = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
return mCursor.getLong(ArticleLoader.Query._ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View view = getLayoutInflater().inflate(R.layout.list_item_article, parent, false);
|
||||||
|
final ViewHolder vh = new ViewHolder(view);
|
||||||
|
view.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||||
|
ItemsContract.Items.buildItemUri(getItemId(vh.getAdapterPosition()))));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
holder.titleView.setText(mCursor.getString(ArticleLoader.Query.TITLE));
|
||||||
|
holder.subtitleView.setText(
|
||||||
|
DateUtils.getRelativeTimeSpanString(
|
||||||
|
mCursor.getLong(ArticleLoader.Query.PUBLISHED_DATE),
|
||||||
|
System.currentTimeMillis(), DateUtils.HOUR_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_ABBREV_ALL).toString()
|
||||||
|
+ " by "
|
||||||
|
+ mCursor.getString(ArticleLoader.Query.AUTHOR));
|
||||||
|
holder.thumbnailView.setImageUrl(
|
||||||
|
mCursor.getString(ArticleLoader.Query.THUMB_URL),
|
||||||
|
ImageLoaderHelper.getInstance(ArticleListActivity.this).getImageLoader());
|
||||||
|
holder.thumbnailView.setAspectRatio(mCursor.getFloat(ArticleLoader.Query.ASPECT_RATIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mCursor.getCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public DynamicHeightNetworkImageView thumbnailView;
|
||||||
|
public TextView titleView;
|
||||||
|
public TextView subtitleView;
|
||||||
|
|
||||||
|
public ViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
thumbnailView = (DynamicHeightNetworkImageView) view.findViewById(R.id.thumbnail);
|
||||||
|
titleView = (TextView) view.findViewById(R.id.article_title);
|
||||||
|
subtitleView = (TextView) view.findViewById(R.id.article_subtitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||||
|
|
||||||
|
private static final int[] ATTRS = new int[]{
|
||||||
|
android.R.attr.listDivider
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
|
||||||
|
|
||||||
|
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
|
||||||
|
|
||||||
|
private Drawable mDivider;
|
||||||
|
|
||||||
|
private int mOrientation;
|
||||||
|
|
||||||
|
public DividerItemDecoration(Context context, Drawable divider, int orientation) {
|
||||||
|
mDivider = divider;
|
||||||
|
setOrientation(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrientation(int orientation) {
|
||||||
|
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
|
||||||
|
throw new IllegalArgumentException("invalid orientation");
|
||||||
|
}
|
||||||
|
mOrientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
if (mOrientation == VERTICAL_LIST) {
|
||||||
|
drawVertical(c, parent);
|
||||||
|
} else {
|
||||||
|
drawHorizontal(c, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawVertical(Canvas c, RecyclerView parent) {
|
||||||
|
final int left = parent.getPaddingLeft();
|
||||||
|
final int right = parent.getWidth() - parent.getPaddingRight();
|
||||||
|
|
||||||
|
final int childCount = parent.getChildCount();
|
||||||
|
for (int i = 0; i < childCount; i++) {
|
||||||
|
final View child = parent.getChildAt(i);
|
||||||
|
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
|
||||||
|
.getLayoutParams();
|
||||||
|
final int top = child.getBottom() + params.bottomMargin;
|
||||||
|
final int bottom = top + mDivider.getIntrinsicHeight();
|
||||||
|
mDivider.setBounds(left, top, right, bottom);
|
||||||
|
mDivider.draw(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawHorizontal(Canvas c, RecyclerView parent) {
|
||||||
|
final int top = parent.getPaddingTop();
|
||||||
|
final int bottom = parent.getHeight() - parent.getPaddingBottom();
|
||||||
|
|
||||||
|
final int childCount = parent.getChildCount();
|
||||||
|
for (int i = 0; i < childCount; i++) {
|
||||||
|
final View child = parent.getChildAt(i);
|
||||||
|
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
|
||||||
|
.getLayoutParams();
|
||||||
|
final int left = child.getRight() + params.rightMargin;
|
||||||
|
final int right = left + mDivider.getIntrinsicHeight();
|
||||||
|
mDivider.setBounds(left, top, right, bottom);
|
||||||
|
mDivider.draw(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
if (mOrientation == VERTICAL_LIST) {
|
||||||
|
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
|
||||||
|
} else {
|
||||||
|
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import com.example.xyzreader.R;
|
||||||
|
|
||||||
|
|
||||||
|
public class DrawInsetsFrameLayout extends FrameLayout {
|
||||||
|
private Drawable mInsetBackground;
|
||||||
|
private Drawable mTopInsetBackground;
|
||||||
|
private Drawable mBottomInsetBackground;
|
||||||
|
private Drawable mSideInsetBackground;
|
||||||
|
|
||||||
|
private Rect mInsets;
|
||||||
|
private Rect mTempRect = new Rect();
|
||||||
|
private OnInsetsCallback mOnInsetsCallback;
|
||||||
|
|
||||||
|
public DrawInsetsFrameLayout(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawInsetsFrameLayout(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
init(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
final TypedArray a = context.obtainStyledAttributes(attrs,
|
||||||
|
R.styleable.DrawInsetsFrameLayout, defStyle, 0);
|
||||||
|
assert a != null;
|
||||||
|
|
||||||
|
mInsetBackground = a.getDrawable(R.styleable.DrawInsetsFrameLayout_insetBackground);
|
||||||
|
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInsetBackground(Drawable insetBackground) {
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setCallback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insetBackground != null) {
|
||||||
|
insetBackground.setCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
mInsetBackground = insetBackground;
|
||||||
|
postInvalidateOnAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
requestApplyInsets();
|
||||||
|
}
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setCallback(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setCallback(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) {
|
||||||
|
mOnInsetsCallback = onInsetsCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||||
|
insets = super.onApplyWindowInsets(insets);
|
||||||
|
mInsets = new Rect(
|
||||||
|
insets.getSystemWindowInsetLeft(),
|
||||||
|
insets.getSystemWindowInsetTop(),
|
||||||
|
insets.getSystemWindowInsetRight(),
|
||||||
|
insets.getSystemWindowInsetBottom());
|
||||||
|
setWillNotDraw(false);
|
||||||
|
postInvalidateOnAnimation();
|
||||||
|
if (mOnInsetsCallback != null) {
|
||||||
|
mOnInsetsCallback.onInsetsChanged(mInsets);
|
||||||
|
}
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
super.draw(canvas);
|
||||||
|
int width = getWidth();
|
||||||
|
int height = getHeight();
|
||||||
|
|
||||||
|
if (mInsets != null) {
|
||||||
|
// Top
|
||||||
|
mTempRect.set(0, 0, width, mInsets.top);
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setBounds(mTempRect);
|
||||||
|
mInsetBackground.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
mTempRect.set(0, height - mInsets.bottom, width, height);
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setBounds(mTempRect);
|
||||||
|
mInsetBackground.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left
|
||||||
|
mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setBounds(mTempRect);
|
||||||
|
mInsetBackground.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right
|
||||||
|
mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
|
||||||
|
if (mInsetBackground != null) {
|
||||||
|
mInsetBackground.setBounds(mTempRect);
|
||||||
|
mInsetBackground.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface OnInsetsCallback {
|
||||||
|
public void onInsetsChanged(Rect insets);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import com.android.volley.toolbox.NetworkImageView;
|
||||||
|
|
||||||
|
public class DynamicHeightNetworkImageView extends NetworkImageView {
|
||||||
|
private float mAspectRatio = 1.5f;
|
||||||
|
|
||||||
|
public DynamicHeightNetworkImageView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicHeightNetworkImageView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicHeightNetworkImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAspectRatio(float aspectRatio) {
|
||||||
|
mAspectRatio = aspectRatio;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
int measuredWidth = getMeasuredWidth();
|
||||||
|
setMeasuredDimension(measuredWidth, (int) (measuredWidth / mAspectRatio));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.support.v4.util.LruCache;
|
||||||
|
|
||||||
|
import com.android.volley.RequestQueue;
|
||||||
|
import com.android.volley.toolbox.ImageLoader;
|
||||||
|
import com.android.volley.toolbox.Volley;
|
||||||
|
|
||||||
|
public class ImageLoaderHelper {
|
||||||
|
private static ImageLoaderHelper sInstance;
|
||||||
|
|
||||||
|
public static ImageLoaderHelper getInstance(Context context) {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new ImageLoaderHelper(context.getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final LruCache<String, Bitmap> mImageCache = new LruCache<String, Bitmap>(20);
|
||||||
|
private ImageLoader mImageLoader;
|
||||||
|
|
||||||
|
private ImageLoaderHelper(Context applicationContext) {
|
||||||
|
RequestQueue queue = Volley.newRequestQueue(applicationContext);
|
||||||
|
ImageLoader.ImageCache imageCache = new ImageLoader.ImageCache() {
|
||||||
|
@Override
|
||||||
|
public void putBitmap(String key, Bitmap value) {
|
||||||
|
mImageCache.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap getBitmap(String key) {
|
||||||
|
return mImageCache.get(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mImageLoader = new ImageLoader(queue, imageCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageLoader getImageLoader() {
|
||||||
|
return mImageLoader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple {@link LinearLayout} subclass that has a maxWidth
|
||||||
|
*/
|
||||||
|
public class MaxWidthLinearLayout extends LinearLayout {
|
||||||
|
private static final int[] ATTRS = {
|
||||||
|
android.R.attr.maxWidth
|
||||||
|
};
|
||||||
|
|
||||||
|
private int mMaxWidth = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
init(context, attrs, defStyle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
init(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
|
||||||
|
mMaxWidth = a.getLayoutDimension(0, Integer.MAX_VALUE);
|
||||||
|
a.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int newSpecWidth = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth);
|
||||||
|
widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSpecWidth, MeasureSpec.getMode(widthMeasureSpec));
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.xyzreader.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom ScrollView that can accept a scroll listener.
|
||||||
|
*/
|
||||||
|
public class ObservableScrollView extends ScrollView {
|
||||||
|
private Callbacks mCallbacks;
|
||||||
|
|
||||||
|
public ObservableScrollView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
||||||
|
super.onScrollChanged(l, t, oldl, oldt);
|
||||||
|
if (mCallbacks != null) {
|
||||||
|
mCallbacks.onScrollChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||||
|
super.onLayout(changed, l, t, r, b);
|
||||||
|
int scrollY = getScrollY();
|
||||||
|
// hack to call onScrollChanged on screen rotate
|
||||||
|
if (scrollY > 0 && mCallbacks != null) {
|
||||||
|
mCallbacks.onScrollChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int computeVerticalScrollRange() {
|
||||||
|
return super.computeVerticalScrollRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallbacks(Callbacks listener) {
|
||||||
|
mCallbacks = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface Callbacks {
|
||||||
|
public void onScrollChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
38
XYZReader/src/main/res/anim-v21/fab_state_list_anim.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Copyright 2015 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:state_enabled="true"
|
||||||
|
android:state_pressed="true">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="@android:integer/config_shortAnimTime"
|
||||||
|
android:propertyName="translationZ"
|
||||||
|
android:valueTo="@dimen/fab_press_translation_z"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="@android:integer/config_shortAnimTime"
|
||||||
|
android:propertyName="translationZ"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
BIN
XYZReader/src/main/res/drawable-nodpi/empty_detail.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2015 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#8fff">
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="#fff" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
BIN
XYZReader/src/main/res/drawable-xxhdpi/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
XYZReader/src/main/res/drawable-xxhdpi/ic_share.png
Normal file
|
After Width: | Height: | Size: 698 B |
BIN
XYZReader/src/main/res/drawable-xxhdpi/logo.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
24
XYZReader/src/main/res/drawable/add_fab_background.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2015 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="@color/theme_accent" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:drawable="@drawable/white_selectable_item_background_circle" />
|
||||||
|
</layer-list>
|
||||||
7
XYZReader/src/main/res/drawable/padded_divider.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:insetRight="112dp">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="#1E000000" />
|
||||||
|
<size android:width="1dp" android:height="1dp" />
|
||||||
|
</shape>
|
||||||
|
</inset>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<gradient
|
||||||
|
android:angle="-90"
|
||||||
|
android:startColor="#3000"
|
||||||
|
android:endColor="#0000"
|
||||||
|
android:type="linear"
|
||||||
|
android:useLevel="false" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2014 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:enterFadeDuration="100"
|
||||||
|
android:exitFadeDuration="100">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="#3fff" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_focused="true">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="#2fff" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:drawable="@android:color/transparent" />
|
||||||
|
</selector>
|
||||||
25
XYZReader/src/main/res/layout/activity_article_detail.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v4.view.ViewPager
|
||||||
|
android:id="@+id/pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/up_container"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?actionBarSize">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||||
|
android:id="@+id/action_up"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/up"
|
||||||
|
android:src="@drawable/ic_arrow_back" />
|
||||||
|
</FrameLayout>
|
||||||
|
</FrameLayout>
|
||||||
49
XYZReader/src/main/res/layout/activity_article_list.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.v4.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipe_refresh_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:scrollbarStyle="outsideOverlay"
|
||||||
|
android:paddingTop="116dp"
|
||||||
|
android:paddingLeft="@dimen/list_side_margin"
|
||||||
|
android:paddingRight="@dimen/list_side_margin"
|
||||||
|
android:paddingBottom="28dp" />
|
||||||
|
</android.support.v4.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/toolbar_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="112dp"
|
||||||
|
android:background="?colorPrimary"
|
||||||
|
android:elevation="8dp">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||||
|
android:layout_marginLeft="@dimen/list_toolbar_side_margin"
|
||||||
|
android:layout_marginRight="@dimen/list_toolbar_side_margin"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?actionBarSize"
|
||||||
|
android:layout_gravity="bottom">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/logo"
|
||||||
|
android:contentDescription="@string/app_name" />
|
||||||
|
|
||||||
|
</android.support.v7.widget.Toolbar>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
107
XYZReader/src/main/res/layout/fragment_article_detail.xml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<com.example.xyzreader.ui.DrawInsetsFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/draw_insets_frame_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.example.xyzreader.ui.ObservableScrollView
|
||||||
|
android:id="@+id/scrollview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/photo_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/photo_placeholder">
|
||||||
|
<!--suppress AndroidLintContentDescription -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/photo"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="112dp"
|
||||||
|
android:background="@drawable/photo_background_protection" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<com.example.xyzreader.ui.MaxWidthLinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:maxWidth="@dimen/detail_card_max_width"
|
||||||
|
android:background="#fff"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:layout_marginTop="@dimen/detail_card_top_margin">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/meta_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#333"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="@dimen/detail_inner_horiz_margin"
|
||||||
|
android:paddingRight="@dimen/detail_inner_horiz_margin"
|
||||||
|
android:paddingTop="@dimen/detail_metabar_vert_padding"
|
||||||
|
android:paddingBottom="@dimen/detail_metabar_vert_padding"
|
||||||
|
android:layout_marginBottom="@dimen/detail_metabar_vert_padding">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/article_title"
|
||||||
|
style="?android:attr/textAppearanceLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="@dimen/detail_metabar_title_bottom_padding"
|
||||||
|
android:fontFamily="sans-serif-condensed"
|
||||||
|
android:textColor="#fff"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="@dimen/detail_title_text_size"
|
||||||
|
android:lineSpacingMultiplier="0.9" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/article_byline"
|
||||||
|
style="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#8fff" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/article_body"
|
||||||
|
style="?android:attr/textAppearanceMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/detail_inner_horiz_margin"
|
||||||
|
android:layout_marginRight="@dimen/detail_inner_horiz_margin"
|
||||||
|
android:paddingBottom="@dimen/detail_body_bottom_margin"
|
||||||
|
android:textColor="@color/ltgray"
|
||||||
|
android:textColorLink="@color/theme_accent"
|
||||||
|
android:textSize="@dimen/detail_body_text_size"
|
||||||
|
android:lineSpacingMultiplier="@fraction/detail_body_line_spacing_multiplier" />
|
||||||
|
|
||||||
|
</com.example.xyzreader.ui.MaxWidthLinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
</com.example.xyzreader.ui.ObservableScrollView>
|
||||||
|
|
||||||
|
<ImageButton android:id="@+id/share_fab"
|
||||||
|
android:stateListAnimator="@anim/fab_state_list_anim"
|
||||||
|
android:background="@drawable/add_fab_background"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginRight="@dimen/fab_margin"
|
||||||
|
android:layout_marginBottom="@dimen/fab_margin"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:src="@drawable/ic_share"
|
||||||
|
android:elevation="@dimen/fab_elevation"
|
||||||
|
android:contentDescription="@string/action_share" />
|
||||||
|
|
||||||
|
</com.example.xyzreader.ui.DrawInsetsFrameLayout>
|
||||||
52
XYZReader/src/main/res/layout/list_item_article.xml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:foreground="?selectableItemBackground">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!--suppress AndroidLintContentDescription -->
|
||||||
|
<com.example.xyzreader.ui.DynamicHeightNetworkImageView
|
||||||
|
android:id="@+id/thumbnail"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:background="@color/photo_placeholder" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/article_title"
|
||||||
|
style="@style/TextAppearance.AppCompat.Subhead"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:textColor="@color/ltgray"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="4" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/article_subtitle"
|
||||||
|
style="@style/TextAppearance.AppCompat.Body1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:textColor="#6000"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.CardView>
|
||||||
5
XYZReader/src/main/res/menu/main.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/refresh"
|
||||||
|
android:title="@string/action_refresh" />
|
||||||
|
</menu>
|
||||||
BIN
XYZReader/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
XYZReader/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
XYZReader/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
XYZReader/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
XYZReader/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
5
XYZReader/src/main/res/values-land/macro_layout.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- detail -->
|
||||||
|
<dimen name="detail_photo_height">224dp</dimen>
|
||||||
|
<dimen name="detail_inner_horiz_margin">112dp</dimen>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- detail -->
|
||||||
|
<bool name="detail_is_card">true</bool>
|
||||||
|
|
||||||
|
<dimen name="detail_photo_height">600dp</dimen>
|
||||||
|
<dimen name="detail_card_top_margin">256dp</dimen>
|
||||||
|
</resources>
|
||||||
7
XYZReader/src/main/res/values-sw600dp/dimens.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- detail -->
|
||||||
|
<dimen name="detail_title_text_size">42sp</dimen>
|
||||||
|
<dimen name="detail_body_text_size">20sp</dimen>
|
||||||
|
<item name="detail_body_line_spacing_multiplier" format="float" type="fraction">1.3</item>
|
||||||
|
<dimen name="fab_margin">24dp</dimen>
|
||||||
|
</resources>
|
||||||
9
XYZReader/src/main/res/values-sw600dp/macro_layout.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- detail -->
|
||||||
|
<dimen name="detail_photo_height">384dp</dimen>
|
||||||
|
<dimen name="detail_card_top_margin">384dp</dimen>
|
||||||
|
<dimen name="detail_inner_horiz_margin">64dp</dimen>
|
||||||
|
<dimen name="detail_metabar_vert_padding">32dp</dimen>
|
||||||
|
<dimen name="detail_metabar_title_bottom_padding">8dp</dimen>
|
||||||
|
<dimen name="detail_body_bottom_margin">96dp</dimen>
|
||||||
|
</resources>
|
||||||
9
XYZReader/src/main/res/values-w800dp/macro_layout.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- list -->
|
||||||
|
<dimen name="list_side_margin">64dp</dimen>
|
||||||
|
<dimen name="list_toolbar_side_margin">52dp</dimen> <!-- minus 64 - 12 -->
|
||||||
|
<integer name="list_column_count">3</integer>
|
||||||
|
|
||||||
|
<!-- detail -->
|
||||||
|
<dimen name="detail_card_max_width">704dp</dimen>
|
||||||
|
</resources>
|
||||||
5
XYZReader/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<declare-styleable name="DrawInsetsFrameLayout">
|
||||||
|
<attr name="insetBackground" format="color|reference" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
||||||
25
XYZReader/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- File created by the Android Action Bar Style Generator
|
||||||
|
|
||||||
|
Copyright (C) 2012 readyState Software Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<color name="photo_placeholder">#ccc</color>
|
||||||
|
|
||||||
|
<color name="theme_primary">#04561A</color>
|
||||||
|
<color name="theme_primary_dark">#04561A</color>
|
||||||
|
<color name="theme_accent">#04561A</color>
|
||||||
|
<color name="ltgray">#ffe0e0e0</color>
|
||||||
|
</resources>
|
||||||
11
XYZReader/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- detail -->
|
||||||
|
<dimen name="detail_title_text_size">32sp</dimen>
|
||||||
|
<dimen name="detail_body_text_size">22sp</dimen>
|
||||||
|
<item name="detail_body_line_spacing_multiplier" format="float" type="fraction">1.25</item>
|
||||||
|
|
||||||
|
<dimen name="fab_elevation">2dp</dimen>
|
||||||
|
<dimen name="fab_press_translation_z">6dp</dimen>
|
||||||
|
<dimen name="fab_margin">24dp</dimen>
|
||||||
|
|
||||||
|
</resources>
|
||||||
17
XYZReader/src/main/res/values/macro_layout.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- list -->
|
||||||
|
<dimen name="list_side_margin">4dp</dimen>
|
||||||
|
<dimen name="list_toolbar_side_margin">0dp</dimen>
|
||||||
|
<integer name="list_column_count">2</integer>
|
||||||
|
|
||||||
|
<!-- detail -->
|
||||||
|
<bool name="detail_is_card">false</bool>
|
||||||
|
<dimen name="detail_photo_height">304dp</dimen>
|
||||||
|
<dimen name="detail_card_top_margin">@dimen/detail_photo_height</dimen>
|
||||||
|
<dimen name="detail_inner_horiz_margin">16dp</dimen>
|
||||||
|
<dimen name="detail_card_max_width">10000dp</dimen> <!-- force match_parent -->
|
||||||
|
<dimen name="detail_metabar_vert_padding">16dp</dimen>
|
||||||
|
<dimen name="detail_metabar_title_bottom_padding">4dp</dimen>
|
||||||
|
<dimen name="detail_body_top_margin">16dp</dimen>
|
||||||
|
<dimen name="detail_body_bottom_margin">88dp</dimen>
|
||||||
|
</resources>
|
||||||
12
XYZReader/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">XYZ Reader</string>
|
||||||
|
<string name="welcome_xyz_reader">Welcome to XYZ Reader!</string>
|
||||||
|
|
||||||
|
<string name="action_refresh">Refresh</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
|
||||||
|
<string name="up">Up</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
28
XYZReader/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!-- File created by the Android Action Bar Style Generator
|
||||||
|
|
||||||
|
Copyright (C) 2012 readyState Software Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.Bacon" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Bacon.Detail">
|
||||||
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
1
build.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Sat Apr 18 23:16:49 EDT 2015
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=http\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||||
164
gradlew
vendored
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||||
|
if $cygwin ; then
|
||||||
|
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >&-
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >&-
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
90
gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
1
project/gradle.scala
Normal file
@@ -0,0 +1 @@
|
|||||||
|
object Build extends android.GradleBuild
|
||||||
2
project/plugins.sbt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
addSbtPlugin("com.hanhuy.sbt" % "android-protify" % "1.1.12")
|
||||||
|
addSbtPlugin("com.hanhuy.sbt" % "android-gradle-build" % "1.1.7")
|
||||||
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
|||||||
|
include ':XYZReader'
|
||||||