├── .gitignore
├── README.md
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── yodel-sample
├── AndroidManifest.xml
├── build.gradle
├── libs
└── YahooSearch-0.1.0-with-volley.aar
├── proguard-project-debug.txt
├── proguard-project.txt
├── res
├── animator
│ ├── fade_hide.xml
│ └── fade_show.xml
├── drawable-v21
│ └── list_selector_background.xml
├── drawable-xhdpi
│ ├── ic_gallery.png
│ ├── ic_like.png
│ ├── ic_reblog.png
│ ├── ic_search_blue.png
│ ├── ic_search_white.png
│ └── ic_video.png
├── drawable-xxhdpi
│ └── bottom_shadow.9.png
├── drawable
│ ├── list_selector_background.xml
│ └── toolbar_gradient_bg.xml
├── layout-w580dp
│ ├── list_item_ad_card.xml
│ └── list_item_post_card.xml
├── layout
│ ├── activity_image_gallery.xml
│ ├── activity_main.xml
│ ├── activity_post_detail.xml
│ ├── fragment_full_image.xml
│ ├── fragment_main.xml
│ ├── fragment_post_detail.xml
│ ├── list_item_ad_card.xml
│ ├── list_item_post_card.xml
│ ├── list_item_post_search.xml
│ ├── view_caption_pager_indicator.xml
│ ├── view_more_web_footer.xml
│ ├── view_search_tool_button.xml
│ ├── view_toolbar.xml
│ ├── view_transparent_toolbar.xml
│ └── view_ysearch_header.xml
├── menu
│ └── main.xml
├── mipmap-xxhdpi
│ └── ic_launcher.png
├── values-v21
│ └── styles.xml
├── values-w820dp
│ └── dimens.xml
├── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── placeholder_strings.xml
│ ├── strings.xml
│ └── styles.xml
└── xml
│ └── searchable.xml
└── src
└── com
└── yahoo
└── mobile
└── client
└── android
└── yodel
├── NativeAdFetcher.java
├── NativeTestAppApplication.java
├── feed
└── TumblrFeedManager.java
├── ui
├── FullImageFragment.java
├── ImageGalleryActivity.java
├── MainActivity.java
├── PostDetailActivity.java
├── PostDetailFragment.java
├── PostListFragment.java
├── PostSearchActivity.java
└── widgets
│ ├── AspectRatioImageView.java
│ ├── CaptionViewPagerIndicator.java
│ ├── CustomSearchViewHolder.java
│ ├── SearchToolButton.java
│ └── adapters
│ ├── BaseAdAdapter.java
│ ├── GalleryPagerAdapter.java
│ ├── PostDetailPagerAdapter.java
│ ├── PostListAdapter.java
│ └── PostSearchListAdapter.java
└── utils
├── AnalyticsHelper.java
├── DateTimeUtil.java
├── ImageLoader.java
├── MemoryCache.java
└── PostDataLoader.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
5 |
6 | ## Directory-based project format
7 | .idea/
8 | # if you remove the above rule, at least ignore user-specific stuff:
9 | # .idea/workspace.xml
10 | # .idea/tasks.xml
11 | # and these sensitive or high-churn files:
12 | # .idea/dataSources.ids
13 | # .idea/dataSources.xml
14 | # .idea/sqlDataSources.xml
15 | # .idea/dynamic.xml
16 |
17 | ## File-based project format
18 | *.ipr
19 | *.iml
20 | *.iws
21 |
22 | ## Additional for IntelliJ
23 | out/
24 |
25 | # generated by mpeltonen/sbt-idea plugin
26 | .idea_modules/
27 |
28 | # generated by JIRA plugin
29 | atlassian-ide-plugin.xml
30 |
31 | # built application files
32 | *.apk
33 | *.ap_
34 |
35 | # files for the dex VM
36 | *.dex
37 |
38 | # Java class files
39 | *.class
40 |
41 | # generated files
42 | build/
43 |
44 | # Local configuration file (sdk path, etc)
45 | local.properties
46 |
47 | # Gradle
48 | .gradle
49 |
50 | # Flurry build outputs
51 | build_jenkins/
52 | build_ant
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Yahoo Mobile Developer Conference Sample App - Android
2 |
3 | This project showcases some of the new mobile development tools released by Yahoo during the 2015
4 | [Mobile Developer Conference](http://yahoomobiledevcon.tumblr.com/)
5 |
6 | ## Features
7 |
8 | - Showcases integration of Flurry native ads, along with best practices for integrating native
9 | ads in your Android apps
10 |
11 | ## Requirements for working with the source:
12 |
13 | - Android Studio
14 | - Android SDK Platform Tools r21 or later
15 | - Android SDK Build Tools r21 or later
16 | - Runtime of Android 4.1 (API 16) or later
17 | - Tumblr Developer consumer key and secret
18 |
19 | ## Gemini native ads
20 | To see best practices for integrating native ads into your app, look through the following classes:
21 |
22 | - [GeminiAdFetcher](yodel-sample/src/com/yahoo/mobile/client/android/yodel/GeminiAdFetcher.java): This
23 | shows best practices for fetching and storing native ads to be used in an Android app
24 | - [BaseAdAdapter](yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/adapters/BaseAdAdapter.java):
25 | This shows best practices for integrating native ads into a list/stream of data
26 |
27 | For more info on getting started with Flurry for Android, see
28 | [here](https://developer.yahoo.com/flurry/docs/analytics/gettingstarted/android/).
29 |
30 | ## Copyright
31 |
32 | Copyright 2015 Yahoo Inc. All rights reserved.
33 |
34 | Licensed under the Apache License, Version 2.0 (the "License");
35 | you may not use this file except in compliance with the License.
36 | You may obtain a copy of the License at
37 |
38 | http://www.apache.org/licenses/LICENSE-2.0
39 |
40 | Unless required by applicable law or agreed to in writing, software
41 | distributed under the License is distributed on an "AS IS" BASIS,
42 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43 | See the License for the specific language governing permissions and
44 | limitations under the License.
45 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:1.5.0'
7 | }
8 | }
9 |
10 | allprojects {
11 | repositories {
12 | jcenter()
13 | }
14 | gradle.projectsEvaluated {
15 | tasks.withType(JavaCompile) {
16 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 01 12:08:07 PST 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':yodel-sample'
--------------------------------------------------------------------------------
/yodel-sample/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
52 |
55 |
56 |
60 |
63 |
64 |
65 |
68 |
69 |
70 |
73 |
74 |
75 |
78 |
79 |
80 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/yodel-sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | repositories {
4 | mavenCentral()
5 | flatDir {
6 | dirs 'libs'
7 | }
8 |
9 | repositories {
10 | jcenter()
11 | }
12 | }
13 |
14 | dependencies {
15 |
16 | compile 'com.yahoo.mobile.client.share.search:YahooSearch-0.1.0-with-volley@aar'
17 | compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' // Used by YSearch
18 | compile 'com.tumblr:jumblr:0.0.11'
19 |
20 | // Required for Flurry Analytics integration
21 | compile 'com.flurry.android:analytics:8.0.1@aar'
22 | // Optional - If you want to use the Ads SDK
23 | compile 'com.flurry.android:ads:8.0.1@aar'
24 |
25 | compile 'com.android.support:cardview-v7:25.3.1'
26 | compile 'com.android.support:support-v4:25.3.1'
27 | compile 'com.google.android.gms:play-services-ads:11.0.4'
28 | compile 'com.android.support:appcompat-v7:25.3.1'
29 | compile 'com.google.code.gson:gson:2.7'
30 | }
31 |
32 | android {
33 | compileSdkVersion 25
34 | buildToolsVersion '25.0.0'
35 |
36 | defaultConfig {
37 | applicationId "com.flurry.sample.gemini"
38 | minSdkVersion 16
39 | targetSdkVersion 23
40 | versionCode 1
41 | versionName "2.0"
42 | }
43 |
44 | compileOptions {
45 | sourceCompatibility JavaVersion.VERSION_1_7
46 | targetCompatibility JavaVersion.VERSION_1_7
47 | }
48 |
49 | sourceSets {
50 | main {
51 | manifest.srcFile 'AndroidManifest.xml'
52 | java.srcDirs = ['src']
53 | resources.srcDirs = ['src']
54 | aidl.srcDirs = ['src']
55 | renderscript.srcDirs = ['src']
56 | res.srcDirs = ['res']
57 | assets.srcDirs = ['assets']
58 | }
59 |
60 | androidTest {
61 | manifest.srcFile 'tests/AndroidManifest.xml'
62 | java.srcDirs = ['tests/src']
63 | resources.srcDirs = ['tests/src']
64 | aidl.srcDirs = ['tests/src']
65 | renderscript.srcDirs = ['tests/src']
66 | res.srcDirs = ['tests/res']
67 | assets.srcDirs = ['tests/assets']
68 | }
69 | }
70 |
71 | packagingOptions {
72 | exclude 'META-INF/DEPENDENCIES.txt'
73 | exclude 'META-INF/LICENSE.txt'
74 | exclude 'META-INF/NOTICE.txt'
75 | exclude 'META-INF/ASL2.0'
76 | }
77 |
78 | buildTypes {
79 | debug {
80 | minifyEnabled false
81 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt', 'proguard-project-debug.txt'
82 | }
83 |
84 | release {
85 | minifyEnabled true
86 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/yodel-sample/libs/YahooSearch-0.1.0-with-volley.aar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/libs/YahooSearch-0.1.0-with-volley.aar
--------------------------------------------------------------------------------
/yodel-sample/proguard-project-debug.txt:
--------------------------------------------------------------------------------
1 | # Project-specific ProGuard settings for debug
2 |
3 | -dontobfuscate
--------------------------------------------------------------------------------
/yodel-sample/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # Project-specific ProGuard settings
2 |
3 | # Keep the source file and line number table so we get decipherable stack traces -->
4 | -renamesourcefileattribute SourceFile
5 | -keepattributes Exceptions, SourceFile, LineNumberTable, Signature
6 |
7 | # Preserve Flurry classes
8 | -keep class com.flurry.** { *; }
9 | -dontwarn com.flurry.**
10 | -keepattributes *Annotation*,EnclosingMethod
11 | -keepclasseswithmembers class * {
12 | public (android.content.Context, android.util.AttributeSet, int);
13 | }
14 |
15 | # Appcompat
16 |
17 | # support-v4
18 | -dontwarn android.support.v4.**
19 | -keep class android.support.v4.app.** { *; }
20 | -keep interface android.support.v4.app.** { *; }
21 | -keep class android.support.v4.widget.** { *; }
22 | -keep interface android.support.v4.widget.** { *; }
23 |
24 | # support-v7
25 | -dontwarn android.support.v7.**
26 | -keep class android.support.v7.app.** { *; }
27 | -keep interface android.support.v7.app.** { *; }
28 | -keep class android.support.v7.appcompat.** { *; }
29 | -keep class android.support.v7.widget.SearchView { *; }
30 |
31 | # Preserve GMS classes
32 | -dontwarn com.google.android.gms.**
33 | -dontwarn com.android.volley.toolbox.**
34 | -dontwarn com.yahoo.data.**
35 |
36 | -keep class * extends java.util.ListResourceBundle {
37 | protected Object[][] getContents();
38 | }
39 |
40 | -keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
41 | public static final *** NULL;
42 | }
43 |
44 | -keepnames @com.google.android.gms.common.annotation.KeepName class *
45 | -keepclassmembernames class * {
46 | @com.google.android.gms.common.annotation.KeepName *;
47 | }
48 |
49 | -keepnames class * implements android.os.Parcelable {
50 | public static final ** CREATOR;
51 | }
52 |
53 | -keep class com.google.android.gms.ads.** { *; }
54 | -dontwarn com.google.android.gms.ads.**
55 |
56 | -dontwarn com.google.android.gms.R*
57 |
58 | # Preserve InMobi Ads classes
59 | -keep class com.inmobi.** { *; }
60 | -dontwarn com.inmobi.**
61 |
62 | # Preserve Millennial Ads classes
63 | -keep class com.millennialmedia.** { *; }
64 | -dontwarn com.millennialmedia.**
65 |
66 | # Preserve Facebbok Audience Network Ads classes
67 | -keep class com.facebook.** { *; }
68 | -dontwarn com.facebook.**
69 |
70 | # Ignore notes about missing unused classes dynamically referenced by com.millennialmedia and google.gson
71 | -dontnote sun.misc.Unsafe
72 |
73 | # Ignore notes about missing unused classes *.ILicensingService
74 | -dontnote com.google.vending.licensing.ILicensingService
75 | -dontnote com.android.vending.licensing.ILicensingService
76 |
77 | # Preserve YSearch SDK classes
78 | -keep class com.yahoo.mobile.client.share.search.** { *; }
79 | -keep interface com.yahoo.mobile.client.share.search.** { *; }
80 | -keep class * implements com.yahoo.mobile.client.share.search.interfaces.IFactory
81 | -dontwarn com.yahoo.mobile.client.share.search.**
82 |
83 | # If your project uses WebView with JS, uncomment the following
84 | # and specify the fully qualified class name to the JavaScript interface
85 | # class:
86 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
87 | # public *;
88 | #}
89 |
--------------------------------------------------------------------------------
/yodel-sample/res/animator/fade_hide.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/yodel-sample/res/animator/fade_show.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-v21/list_selector_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xhdpi/ic_gallery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xhdpi/ic_gallery.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xhdpi/ic_like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xhdpi/ic_like.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xhdpi/ic_reblog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xhdpi/ic_reblog.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xhdpi/ic_search_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xhdpi/ic_search_blue.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xhdpi/ic_search_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xhdpi/ic_search_white.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xhdpi/ic_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xhdpi/ic_video.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable-xxhdpi/bottom_shadow.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/drawable-xxhdpi/bottom_shadow.9.png
--------------------------------------------------------------------------------
/yodel-sample/res/drawable/list_selector_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/yodel-sample/res/drawable/toolbar_gradient_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout-w580dp/list_item_ad_card.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
22 |
27 |
37 |
38 |
48 |
49 |
59 |
60 |
67 |
68 |
80 |
81 |
92 |
93 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout-w580dp/list_item_post_card.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
22 |
27 |
37 |
38 |
48 |
49 |
59 |
60 |
68 |
69 |
81 |
82 |
93 |
94 |
100 |
101 |
111 |
112 |
120 |
121 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/activity_image_gallery.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
11 |
12 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/activity_post_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/fragment_full_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/fragment_post_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
17 |
25 |
32 |
39 |
40 |
50 |
51 |
62 |
73 |
74 |
81 |
82 |
92 |
93 |
103 |
104 |
112 |
113 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/list_item_ad_card.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
20 |
26 |
27 |
37 |
38 |
45 |
46 |
58 |
59 |
72 |
73 |
82 |
83 |
95 |
96 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/list_item_post_card.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
20 |
26 |
27 |
37 |
38 |
46 |
47 |
59 |
60 |
73 |
74 |
86 |
87 |
93 |
94 |
105 |
106 |
115 |
116 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/list_item_post_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
19 |
29 |
30 |
44 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/view_caption_pager_indicator.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
22 |
23 |
32 |
33 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/view_more_web_footer.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/view_search_tool_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
22 |
30 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/view_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/view_transparent_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/yodel-sample/res/layout/view_ysearch_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
17 |
18 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/yodel-sample/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/yodel-sample/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flurry/YodelAndroidApp/175349dc0351cd0daf46a4175242470f0b23c3b9/yodel-sample/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/yodel-sample/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/yodel-sample/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/yodel-sample/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/yodel-sample/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFF
4 | #EEE
5 | #B2B2B2
6 | #EDEDF1
7 |
8 | #5A00C8
9 | #4B048B
10 | #D84B048B
11 | #D8ED2023
12 | #FF8B12
13 | #0D9AF8
14 | #93004F
15 |
--------------------------------------------------------------------------------
/yodel-sample/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 | -16dp
7 | -16dp
8 |
9 | 8dp
10 | 16dp
11 |
12 | 48dp
13 |
14 | 12sp
15 | 14sp
16 | 18sp
17 | 20sp
18 |
19 |
20 |
--------------------------------------------------------------------------------
/yodel-sample/res/values/placeholder_strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Yahoo
4 | Lorem ipsum dolor sit amet
5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit.Suspendisse varius purus eu erat mollis, eget scelerisque ante vulputate. Vivamus rutrum.
6 | 2 days ago
7 | 3
8 | #lorem #ipsum #dolor #sit #amet #consectetur #adipiscing
9 |
--------------------------------------------------------------------------------
/yodel-sample/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Yodel Sample
5 |
6 | Sponsored
7 |
8 | Fetching posts
9 | Network not available
10 | Retry
11 | Cancel
12 |
13 | Search
14 |
15 | Learn more: %1$s
17 | ]]>
18 |
19 | Search for content
20 | Search the web
21 | Posts tagged \"<b>%1$s</b>\"
22 | MORE FROM WEB
23 | %1$s of %2$s
24 | Just Now
25 |
26 |
27 | - A minute ago
28 | - %d minutes ago
29 |
30 |
31 | - An hour ago
32 | - %d hours ago
33 |
34 |
35 | - Yesterday
36 | - %d days ago
37 |
38 |
39 |
--------------------------------------------------------------------------------
/yodel-sample/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
13 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/yodel-sample/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/NativeAdFetcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel;
18 |
19 | import android.content.Context;
20 | import android.util.Log;
21 |
22 | import com.flurry.android.ads.FlurryAdErrorType;
23 | import com.flurry.android.ads.FlurryAdNative;
24 | import com.flurry.android.ads.FlurryAdNativeListener;
25 | import com.flurry.android.ads.FlurryAdTargeting;
26 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
27 |
28 | import java.lang.ref.WeakReference;
29 | import java.util.ArrayList;
30 | import java.util.HashMap;
31 | import java.util.List;
32 | import java.util.Map;
33 |
34 | /**
35 | * Handles fetching, caching, and destroying of native gemini ads.
36 | *
37 | */
38 | public class NativeAdFetcher {
39 | private static final String LOG_TAG = NativeAdFetcher.class.getSimpleName();
40 | /**
41 | * Maximum number of ads to prefetch.
42 | */
43 | private static final int PREFETCHED_ADS_SIZE = 5;
44 | /**
45 | * Maximum number of times to try fetch an ad after failed attempts.
46 | */
47 | private static final int MAX_FETCH_ATTEMPT = 4;
48 | private List mAdNativeListeners = new ArrayList<>();
49 | private List mPrefetchedAdList = new ArrayList<>();
50 | private List mFetchingAdsList = new ArrayList<>();
51 | private Map adMapAtIndex = new HashMap<>();
52 | private int mNoOfFetchedAds;
53 | private int mFetchFailCount;
54 | private WeakReference mContext = new WeakReference<>(null);
55 | private FlurryAdNativeListener mAdNativeListener = new FlurryAdNativeListener() {
56 | @Override
57 | public synchronized void onFetched(FlurryAdNative adNative) {
58 | Log.i(LOG_TAG, "onFetched");
59 | if (canUseThisAd(adNative)) {
60 | mPrefetchedAdList.add(adNative);
61 | mNoOfFetchedAds++;
62 | }
63 | if (mFetchingAdsList.contains(adNative)) {
64 | mFetchingAdsList.remove(adNative);
65 | }
66 | mFetchFailCount = 0;
67 | ensurePrefetchAmount();
68 |
69 | notifyObserversOfAdSizeChange();
70 | }
71 |
72 | @Override
73 | public void onShowFullscreen(FlurryAdNative adNative) { } // Do nothing
74 |
75 | @Override
76 | public void onCloseFullscreen(FlurryAdNative adNative) {
77 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_AD_CLOSEBUTTON_CLICK, null, false);
78 | }
79 |
80 | @Override
81 | public void onAppExit(FlurryAdNative adNative) { }
82 |
83 | @Override
84 | public void onClicked(FlurryAdNative adNative) {
85 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_STREAM_AD_CLICK, null, false);
86 | }
87 |
88 | @Override
89 | public void onImpressionLogged(FlurryAdNative adNative) { }
90 |
91 | @Override
92 | public void onExpanded(FlurryAdNative flurryAdNative) { }
93 |
94 | @Override
95 | public void onCollapsed(FlurryAdNative flurryAdNative) { }
96 |
97 | @Override
98 | public void onError(FlurryAdNative adNative, FlurryAdErrorType adErrorType, int errorCode) {
99 | if (adErrorType.equals(FlurryAdErrorType.FETCH)) {
100 | Log.i(LOG_TAG, "onFetchFailed " + errorCode);
101 |
102 | if (mFetchingAdsList.contains(adNative)) {
103 | adNative.destroy(); // destroy the native ad, as we are not going to render it.
104 | mFetchingAdsList.remove(adNative); // Remove from the tracking list
105 | }
106 | mFetchFailCount++;
107 | ensurePrefetchAmount();
108 | }
109 | }
110 | };
111 |
112 | /**
113 | * Adds an {@link AdNativeListener} that would be notified for any changes to the native ads
114 | * list.
115 | *
116 | * @param listener the listener to be notified
117 | */
118 | public synchronized void addListener(AdNativeListener listener) {
119 | mAdNativeListeners.add(listener);
120 | }
121 |
122 | /**
123 | * Gets native ad at a particular index in the fetched ads list.
124 | *
125 | * @see #getFetchedAdsCount()
126 | * @param index the index of ad in the fetched ads list
127 | * @return the native ad in the list
128 | */
129 | public synchronized FlurryAdNative getAdForIndex(final int index) {
130 | FlurryAdNative adNative = adMapAtIndex.get(index);
131 |
132 | if (adNative == null && mPrefetchedAdList.size() > 0) {
133 | adNative = mPrefetchedAdList.remove(0);
134 |
135 | if (adNative != null) {
136 | adMapAtIndex.put(index, adNative);
137 | }
138 | }
139 |
140 | ensurePrefetchAmount(); // Make sure we have enough pre-fetched ads
141 |
142 | return adNative;
143 | }
144 |
145 | /**
146 | * Gets the number of ads that have been fetched so far.
147 | *
148 | * @return the number of ads that have been fetched
149 | */
150 | public synchronized int getFetchedAdsCount() {
151 | return mNoOfFetchedAds;
152 | }
153 |
154 | /**
155 | * Fetches a new native ad.
156 | * @param context the current context.
157 | *
158 | * @see #destroyAllAds()
159 | */
160 | public synchronized void prefetchAds(Context context) {
161 | mContext = new WeakReference<>(context);
162 | fetchAd();
163 | }
164 |
165 | /**
166 | * Destroys ads that have been fetched, that are still being fetched and removes all resource
167 | * references that this instance still has. This should only be called when the Activity that
168 | * is showing ads is closing, preferably from the {@link android.app.Activity#onDestroy()}.
169 | *
170 | * The converse of this call is {@link #prefetchAds(android.content.Context)}.
171 | */
172 | public synchronized void destroyAllAds() {
173 | for (FlurryAdNative flurryAdNative : adMapAtIndex.values()) {
174 | flurryAdNative.destroy();
175 | }
176 |
177 | mFetchFailCount = 0;
178 | adMapAtIndex.clear();
179 |
180 | for (FlurryAdNative ad : mPrefetchedAdList) {
181 | ad.destroy();
182 | }
183 | mPrefetchedAdList.clear();
184 | mNoOfFetchedAds = 0;
185 |
186 | for (FlurryAdNative ad : mFetchingAdsList) {
187 | ad.destroy();
188 | }
189 | mFetchingAdsList.clear();
190 |
191 | Log.i(LOG_TAG, "destroyAllAds adList " + adMapAtIndex.size() + " prefetched " +
192 | mPrefetchedAdList.size() + " fetched " + mFetchingAdsList.size());
193 |
194 | mContext.clear();
195 |
196 | notifyObserversOfAdSizeChange();
197 | }
198 |
199 | /**
200 | * Notifies all registered {@link AdNativeListener} of a change in ad data count.
201 | */
202 | private void notifyObserversOfAdSizeChange() {
203 | for (AdNativeListener listener : mAdNativeListeners) {
204 | listener.onAdCountChanged();
205 | }
206 | }
207 |
208 | /**
209 | * Fetches a new native ad.
210 | */
211 | private synchronized void fetchAd() {
212 | Context context = mContext.get();
213 |
214 | if (context != null) {
215 | Log.i(LOG_TAG, "Fetching Ad now");
216 | FlurryAdNative nativeAd = new FlurryAdNative(
217 | context, NativeTestAppApplication.FLURRY_ADSPACE);
218 | nativeAd.setListener(mAdNativeListener);
219 | mFetchingAdsList.add(nativeAd);
220 | nativeAd.fetchAd();
221 | } else {
222 | mFetchFailCount++;
223 | Log.i(LOG_TAG, "Context is null, not fetching Ad");
224 | }
225 | }
226 |
227 | /**
228 | * Ensures that the necessary amount of prefetched native ads are available.
229 | */
230 | private synchronized void ensurePrefetchAmount() {
231 | if (mPrefetchedAdList.size() < PREFETCHED_ADS_SIZE &&
232 | (mFetchFailCount < MAX_FETCH_ATTEMPT)) {
233 | fetchAd();
234 | }
235 | }
236 |
237 | /**
238 | * Determines if the native ad can be used.
239 | *
240 | * @param adNative the native ad object
241 | * @return true
if the ad object can be used, false otherwise
242 | */
243 | private boolean canUseThisAd(FlurryAdNative adNative) {
244 | return adNative != null && adNative.isReady() && !adNative.isExpired();
245 | }
246 |
247 | /**
248 | * Listener that is notified when changes to the list of fetched native ads are made.
249 | */
250 | public interface AdNativeListener {
251 | /**
252 | * Triggered when the number of ads have changed. Adapters that implement this class
253 | * should notify their data views that the dataset has changed.
254 | */
255 | void onAdCountChanged();
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/NativeTestAppApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel;
18 |
19 | import android.app.Application;
20 | import android.net.http.HttpResponseCache;
21 | import android.os.Handler;
22 | import android.os.HandlerThread;
23 | import android.os.Looper;
24 | import android.util.Log;
25 |
26 | import com.flurry.android.FlurryAgent;
27 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
28 | import com.yahoo.mobile.client.share.search.settings.SearchSDKSettings;
29 |
30 | import java.io.File;
31 | import java.io.IOException;
32 |
33 | public class NativeTestAppApplication extends Application {
34 | private static String LOG_TAG = NativeTestAppApplication.class.getSimpleName();
35 |
36 | private static final String FLURRY_APIKEY = "JQVT87W7TGN5W7SWY2FH";
37 | public static final String FLURRY_ADSPACE = "StaticVideoNativeTest";
38 | public static final String YAHOO_SEARCH_APIKEY = "your_api_key";
39 |
40 | private static NativeTestAppApplication sApplication;
41 |
42 | private Handler mMainThreadHandler;
43 |
44 | @Override
45 | public void onCreate() {
46 | super.onCreate();
47 |
48 | // Init Search SDK
49 | SearchSDKSettings.initializeSearchSDKSettings(
50 | new SearchSDKSettings.Builder(YAHOO_SEARCH_APIKEY)
51 | .setVoiceSearchEnabled(true));
52 |
53 | sApplication = this;
54 |
55 | // create handlers
56 | mMainThreadHandler = new Handler(Looper.getMainLooper());
57 | HandlerThread backgroundHandlerThread = new HandlerThread(LOG_TAG);
58 | backgroundHandlerThread.start();
59 |
60 | // http response cache
61 | File httpCacheDir = new File(getCacheDir(), "http");
62 | long httpCacheSize = 100 * 1024 * 1024; // 100 MiB
63 |
64 | try {
65 | HttpResponseCache.install(httpCacheDir, httpCacheSize);
66 | } catch (IOException e) {
67 | AnalyticsHelper.logError(LOG_TAG, "HTTP response cache installation failed", e);
68 | }
69 |
70 | // Init Flurry
71 | new FlurryAgent.Builder()
72 | .withLogEnabled(true)
73 | .withCaptureUncaughtExceptions(true)
74 | .withContinueSessionMillis(10)
75 | .withLogEnabled(true)
76 | .withLogLevel(Log.VERBOSE)
77 |
78 | .build(this, FLURRY_APIKEY);
79 | }
80 |
81 | public static NativeTestAppApplication getInstance() {
82 | return sApplication;
83 | }
84 |
85 | public Handler getMainThreadHandler() {
86 | return mMainThreadHandler;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/feed/TumblrFeedManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.feed;
18 |
19 | import com.google.gson.Gson;
20 | import com.google.gson.reflect.TypeToken;
21 | import com.tumblr.jumblr.JumblrClient;
22 | import com.tumblr.jumblr.types.Blog;
23 | import com.tumblr.jumblr.types.LinkPost;
24 | import com.tumblr.jumblr.types.PhotoPost;
25 | import com.tumblr.jumblr.types.Post;
26 | import com.tumblr.jumblr.types.QuotePost;
27 | import com.tumblr.jumblr.types.TextPost;
28 | import com.tumblr.jumblr.types.VideoPost;
29 |
30 | import java.util.ArrayList;
31 | import java.util.HashMap;
32 | import java.util.List;
33 |
34 | /**
35 | * Class that manages fetching of Tumblr blog posts.
36 | */
37 | public final class TumblrFeedManager {
38 | private final static String TUMBLR_CONSUMER_KEY = "xz2gUnYX7axNd9XsVFIjUyhVElCPxU5pQVpfSV1qZgYxaiz29f";
39 | private final static String TUMBLR_CONSUMER_SECRET = "DqFvBW162BYUF92jCQgrLBzVDngxKy8TAZ3tDBspSE7mz4tqTw";
40 | // Only a limited set of the possible Tumblr post types will be supported
41 | public final static String POST_TYPE_TEXT = "text";
42 | public final static String POST_TYPE_QUOTE = "quote";
43 | public final static String POST_TYPE_PHOTO = "photo";
44 | public final static String POST_TYPE_VIDEO = "video";
45 | public final static String POST_TYPE_LINK = "link";
46 |
47 | private static JumblrClient sTumblrClient;
48 | private final static Blog BLOG_FEED_1;
49 | private final static Blog BLOG_FEED_2;
50 | private final static Blog BLOG_FEED_3;
51 |
52 | private final static int MAX_POSTS_TO_FETCH = 20;
53 |
54 | static {
55 | sTumblrClient = new JumblrClient(TUMBLR_CONSUMER_KEY,
56 | TUMBLR_CONSUMER_SECRET);
57 |
58 | BLOG_FEED_1 = sTumblrClient.blogInfo("archaicwonder.tumblr.com");
59 | BLOG_FEED_2 = sTumblrClient.blogInfo("yahoo.tumblr.com");
60 | BLOG_FEED_3 = sTumblrClient.blogInfo("natgeotravel.tumblr.com");
61 | }
62 |
63 | public static List getRecentPosts() {
64 | HashMap optionsMap = new HashMap<>(1);
65 | optionsMap.put("filter", "text");
66 | optionsMap.put("limit", "20");
67 | optionsMap.put("reblog_info", "true");
68 | List recentPosts = new ArrayList<>(MAX_POSTS_TO_FETCH);
69 | List blog1Posts = BLOG_FEED_1.posts(optionsMap);
70 | List blog2Posts = BLOG_FEED_2.posts(optionsMap);
71 | List blog3Posts = BLOG_FEED_3.posts(optionsMap);
72 | int blog1Counter, blog2Counter, blog3Counter;
73 | blog1Counter = blog2Counter = blog3Counter = 0;
74 |
75 |
76 | while (recentPosts.size() < 20) {
77 | Post nextBlogPost;
78 | if (blog1Posts.get(blog1Counter).getTimestamp() >
79 | blog2Posts.get(blog2Counter).getTimestamp() &&
80 | blog1Posts.get(blog1Counter).getTimestamp() >
81 | blog3Posts.get(blog3Counter).getTimestamp()) {
82 | // If the next post from blog1 is the latest, add it to list
83 | nextBlogPost = blog1Posts.get(blog1Counter++);
84 | /*
85 | * Set the post name to a human-readable title instead of the third-level domain name
86 | * it is currently set from.
87 | */
88 | nextBlogPost.setBlogName(BLOG_FEED_1.getTitle());
89 | } else if (blog2Posts.get(blog2Counter).getTimestamp() >
90 | blog3Posts.get(blog3Counter).getTimestamp()) {
91 | // Else, if the next post from blog2 is the latest, add it to list
92 | nextBlogPost = blog2Posts.get(blog2Counter++);
93 | nextBlogPost.setBlogName(BLOG_FEED_2.getTitle());
94 | } else {
95 | // Else, add the next post from blog3 to the list.
96 | nextBlogPost = blog3Posts.get(blog3Counter++);
97 | nextBlogPost.setBlogName(BLOG_FEED_3.getTitle());
98 | }
99 |
100 | /*
101 | Remove JumblrClient since we are not doing any write operations.
102 | This makes serialization easier.
103 | */
104 | nextBlogPost.setClient(null);
105 | recentPosts.add(nextBlogPost);
106 | }
107 | return recentPosts;
108 | }
109 |
110 | public static List getPostsWithTag(String tag) {
111 | HashMap optionsMap = new HashMap<>(1);
112 | optionsMap.put("filter", "text");
113 | optionsMap.put("type", "photo"); // Limit to only photo posts
114 | optionsMap.put("limit", "5");
115 |
116 | return sTumblrClient.tagged(tag, optionsMap);
117 | }
118 |
119 | public static Post deserializePostJson(String postType, String postJsonString) {
120 | Post post;
121 | switch (postType) {
122 | case POST_TYPE_PHOTO:
123 | post = new Gson().fromJson(postJsonString,
124 | new TypeToken() {
125 | }.getType());
126 | break;
127 | case POST_TYPE_LINK:
128 | post = new Gson().fromJson(postJsonString,
129 | new TypeToken() {
130 | }.getType());
131 | break;
132 | case POST_TYPE_VIDEO:
133 | post = new Gson().fromJson(postJsonString,
134 | new TypeToken() {
135 | }.getType());
136 | break;
137 | case POST_TYPE_TEXT:
138 | post = new Gson().fromJson(postJsonString,
139 | new TypeToken() {
140 | }.getType());
141 | break;
142 | case POST_TYPE_QUOTE:
143 | post = new Gson().fromJson(postJsonString,
144 | new TypeToken() {
145 | }.getType());
146 | break;
147 | default:
148 | post = new Gson().fromJson(postJsonString,
149 | new TypeToken() {
150 | }.getType());
151 | }
152 | return post;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/FullImageFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.os.Bundle;
20 | import android.support.annotation.Nullable;
21 | import android.support.v4.app.Fragment;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.ImageView;
26 |
27 | import com.yahoo.mobile.client.android.yodel.R;
28 | import com.yahoo.mobile.client.android.yodel.utils.ImageLoader;
29 |
30 | public class FullImageFragment extends Fragment {
31 |
32 | private final static String EXTRA_PHOTO_URL = "com.yahoo.mobile.sample.extra.photourl";
33 |
34 | public static FullImageFragment newInstance(String photoUrl) {
35 | FullImageFragment fragment = new FullImageFragment();
36 | Bundle bundle = new Bundle();
37 | bundle.putString(EXTRA_PHOTO_URL, photoUrl);
38 | fragment.setArguments(bundle);
39 | return fragment;
40 | }
41 |
42 | @Override
43 | public View onCreateView(LayoutInflater inflater,
44 | @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
45 | View rootView = inflater.inflate(R.layout.fragment_full_image, container, false);
46 |
47 | ImageView imageView = (ImageView)rootView.findViewById(R.id.full_image);
48 |
49 | String photoUrl = getArguments().getString(EXTRA_PHOTO_URL);
50 | ImageLoader.getInstance().displayImage(photoUrl, imageView);
51 |
52 | return rootView;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/ImageGalleryActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.animation.Animator;
20 | import android.animation.AnimatorInflater;
21 | import android.annotation.SuppressLint;
22 | import android.os.Build;
23 | import android.os.Bundle;
24 | import android.os.Handler;
25 | import android.os.Message;
26 | import android.support.annotation.Nullable;
27 | import android.support.v4.view.ViewPager;
28 | import android.support.v7.app.ActionBarActivity;
29 | import android.support.v7.widget.Toolbar;
30 | import android.view.GestureDetector;
31 | import android.view.MotionEvent;
32 | import android.view.View;
33 | import android.view.ViewTreeObserver;
34 |
35 | import com.yahoo.mobile.client.android.yodel.R;
36 | import com.yahoo.mobile.client.android.yodel.ui.widgets.CaptionViewPagerIndicator;
37 | import com.yahoo.mobile.client.android.yodel.ui.widgets.adapters.GalleryPagerAdapter;
38 | import com.google.gson.Gson;
39 | import com.google.gson.reflect.TypeToken;
40 | import com.tumblr.jumblr.types.Photo;
41 |
42 | import java.util.List;
43 |
44 | public class ImageGalleryActivity extends ActionBarActivity {
45 | public static final String EXTRA_PHOTO_LIST = "com.yahoo.mobile.sample.extra.photolist";
46 |
47 | private final static int HIDE_DELAY = 3000; // 3 seconds
48 | private GalleryPagerAdapter mGalleryPagerAdapter;
49 | private final Handler mHideHandler = new Handler() {
50 | @Override
51 | public void handleMessage(Message msg) {
52 | hideSystemUi();
53 | }
54 | };
55 |
56 | private CaptionViewPagerIndicator mCaptionPagerIndicator;
57 |
58 | @Override
59 | public void onCreate(@Nullable Bundle savedInstanceState) {
60 | super.onCreate(savedInstanceState);
61 | setContentView(R.layout.activity_image_gallery);
62 | setTitle("");
63 |
64 | Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_actionbar);
65 | setSupportActionBar(toolbar);
66 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
67 |
68 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
69 | getWindow().setStatusBarColor(getResources().getColor(android.R.color.black));
70 | }
71 |
72 | final List photos = new Gson().fromJson(getIntent().getStringExtra(EXTRA_PHOTO_LIST),
73 | new TypeToken>() {
74 | }.getType());
75 | mGalleryPagerAdapter = new GalleryPagerAdapter(getSupportFragmentManager(), photos);
76 | mCaptionPagerIndicator = (CaptionViewPagerIndicator)findViewById(R.id.caption_pager_indicator);
77 | mCaptionPagerIndicator.setCurrentItem(0, mGalleryPagerAdapter.getCount());
78 | mCaptionPagerIndicator.setCaption(photos.get(0).getCaption());
79 |
80 | final ViewPager galleryPager = (ViewPager) findViewById(R.id.image_view_pager);
81 | galleryPager.setAdapter(mGalleryPagerAdapter);
82 | galleryPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
83 | @Override
84 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
85 | }
86 |
87 | @Override
88 | public void onPageSelected(int position) {
89 | mCaptionPagerIndicator.setCurrentItem(position, mGalleryPagerAdapter.getCount());
90 | mCaptionPagerIndicator.setCaption(photos.get(position).getCaption());
91 | }
92 |
93 | @Override
94 | public void onPageScrollStateChanged(int state) {
95 | }
96 | });
97 | // Delay any activity transition until the ViewPager is ready to be drawn
98 | supportPostponeEnterTransition();
99 | galleryPager.getViewTreeObserver().addOnPreDrawListener(
100 | new ViewTreeObserver.OnPreDrawListener() {
101 | public boolean onPreDraw() {
102 | galleryPager.getViewTreeObserver().removeOnPreDrawListener(this);
103 | supportStartPostponedEnterTransition();
104 | return true;
105 | }
106 | });
107 | final GestureDetector clickDetector = new GestureDetector(this,
108 | new GestureDetector.SimpleOnGestureListener() {
109 | @Override
110 | public boolean onSingleTapUp(MotionEvent e) {
111 | toggleSystemUiVisibility();
112 | return true;
113 | }
114 | });
115 |
116 | galleryPager.setOnTouchListener(new View.OnTouchListener() {
117 | @Override
118 | public boolean onTouch(View v, MotionEvent event) {
119 | return clickDetector.onTouchEvent(event);
120 | }
121 | });
122 | }
123 |
124 | @Override
125 | protected void onStart() {
126 | super.onStart();
127 |
128 | startUiDelayedHide();
129 | }
130 |
131 | @Override
132 | protected void onPause() {
133 | removePendingHides();
134 |
135 | super.onPause();
136 | }
137 |
138 | @SuppressLint("NewApi")
139 | public void hideSystemUi() {
140 |
141 | getWindow().getDecorView().setSystemUiVisibility(
142 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
143 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
144 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
145 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
146 | | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
147 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
148 |
149 | Animator anim = AnimatorInflater.loadAnimator(this, R.animator.fade_hide);
150 | anim.setTarget(mCaptionPagerIndicator);
151 | anim.start();
152 |
153 | getSupportActionBar().hide();
154 | }
155 |
156 | @SuppressLint("NewApi")
157 | public void showSystemUi() {
158 | getWindow().getDecorView().setSystemUiVisibility(
159 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
160 | | View.SYSTEM_UI_FLAG_FULLSCREEN // keep status bar hidden
161 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
162 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
163 |
164 | Animator anim = AnimatorInflater.loadAnimator(this, R.animator.fade_show);
165 | anim.setTarget(mCaptionPagerIndicator);
166 | anim.start();
167 |
168 | getSupportActionBar().show();
169 | }
170 |
171 | protected void toggleSystemUiVisibility() {
172 | boolean visible = isSystemUiVisible();
173 | if (visible) {
174 | hideSystemUi();
175 | } else {
176 | showSystemUi();
177 | }
178 | }
179 |
180 | protected void startUiDelayedHide() {
181 | getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(
182 | new View.OnSystemUiVisibilityChangeListener() {
183 | @Override
184 | public void onSystemUiVisibilityChange(int flags) {
185 | boolean visible = (flags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
186 | if (visible) {
187 | delayedHide(HIDE_DELAY);
188 | }
189 | }
190 | });
191 | // If system UI is visible, start hiding it after a short delay
192 | int visibilityFlags = getWindow().getDecorView().getSystemUiVisibility();
193 | if ((visibilityFlags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
194 | delayedHide(HIDE_DELAY / 6);
195 | }
196 | }
197 |
198 | protected void removePendingHides() {
199 | mHideHandler.removeMessages(0);
200 | getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(null);
201 | resetSystemUi();
202 | }
203 |
204 | public void resetSystemUi() {
205 | getWindow().getDecorView().setSystemUiVisibility(0);
206 | }
207 |
208 | protected boolean isSystemUiVisible() {
209 | return (getWindow().getDecorView().getSystemUiVisibility()
210 | & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
211 | }
212 |
213 | private void delayedHide(int delayMillis) {
214 | // If less than KitKat, don't worry about setting visibility timer.
215 | // Not that it won't work, but my current implementation is strange on
216 | // API < 18
217 | if (android.os.Build.VERSION.SDK_INT < 18) { return; }
218 |
219 | mHideHandler.removeMessages(0);
220 | mHideHandler.sendEmptyMessageDelayed(0, delayMillis);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.app.SearchManager;
20 | import android.content.ComponentName;
21 | import android.content.Intent;
22 | import android.net.http.HttpResponseCache;
23 | import android.os.Bundle;
24 | import android.support.v7.app.ActionBarActivity;
25 | import android.support.v7.widget.SearchView;
26 | import android.support.v7.widget.Toolbar;
27 | import android.view.Menu;
28 | import android.view.View;
29 |
30 | import com.yahoo.mobile.client.android.yodel.R;
31 | import com.tumblr.jumblr.types.Post;
32 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
33 |
34 | import java.util.HashMap;
35 |
36 | public class MainActivity extends ActionBarActivity implements PostListFragment.Callbacks {
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 |
43 | Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_actionbar);
44 | setSupportActionBar(toolbar);
45 |
46 | if (savedInstanceState == null) {
47 | getSupportFragmentManager().beginTransaction()
48 | .replace(R.id.content_frame, PostListFragment.newInstance())
49 | .commit();
50 | }
51 | }
52 |
53 | @Override
54 | protected void onStart() {
55 | super.onStart();
56 |
57 | // Uncomment the line below if targeting any API level less than API 14
58 | // FlurryAgent.onStartSession(this);
59 | }
60 |
61 | @Override
62 | protected void onStop() {
63 | super.onStop();
64 |
65 | // HTTP response cache
66 | HttpResponseCache cache = HttpResponseCache.getInstalled();
67 | if (cache != null) {
68 | cache.flush();
69 | }
70 |
71 | // Uncomment the line below if targeting any API level less than API 14
72 | // FlurryAgent.onEndSession(this);
73 | }
74 |
75 | @Override
76 | public boolean onCreateOptionsMenu(Menu menu) {
77 | getMenuInflater().inflate(R.menu.main, menu);
78 |
79 | SearchManager searchManager = (SearchManager)getSystemService(SEARCH_SERVICE);
80 | SearchView searchView = (SearchView)menu.findItem(R.id.action_search).getActionView();
81 | if (searchView != null) {
82 | searchView.setSearchableInfo(searchManager.getSearchableInfo(
83 | new ComponentName(this, PostSearchActivity.class)));
84 | searchView.setQueryRefinementEnabled(true);
85 | searchView.setOnSearchClickListener(new View.OnClickListener() {
86 | @Override
87 | public void onClick(View v) {
88 | AnalyticsHelper.logEvent(
89 | AnalyticsHelper.EVENT_STREAM_SEARCH_CLICK, null, false);
90 | }
91 | });
92 | }
93 |
94 | return super.onCreateOptionsMenu(menu);
95 | }
96 |
97 | @Override
98 | public void onPostSelected(Post post, int positionId, View clickedView) {
99 | // Log the event
100 | HashMap eventParams = new HashMap<>(2);
101 | eventParams.put(AnalyticsHelper.PARAM_ARTICLE_ORIGIN, post.getBlogName());
102 | eventParams.put(AnalyticsHelper.PARAM_ARTICLE_TYPE, post.getType());
103 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_STREAM_ARTICLE_CLICK, eventParams, false);
104 |
105 | Intent intent = new Intent(this, PostDetailActivity.class);
106 | intent.putExtra(PostDetailActivity.EXTRA_CURRENT_PAGE_INDEX, positionId);
107 | startActivity(intent);
108 | }
109 |
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/PostDetailActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.app.ActivityOptions;
20 | import android.app.ProgressDialog;
21 | import android.content.Intent;
22 | import android.os.Build;
23 | import android.os.Bundle;
24 | import android.support.v4.app.LoaderManager;
25 | import android.support.v4.content.Loader;
26 | import android.support.v4.view.ViewPager;
27 | import android.support.v7.app.ActionBarActivity;
28 | import android.support.v7.widget.Toolbar;
29 | import android.view.MenuItem;
30 | import android.view.View;
31 |
32 | import com.yahoo.mobile.client.android.yodel.R;
33 | import com.yahoo.mobile.client.android.yodel.feed.TumblrFeedManager;
34 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
35 | import com.yahoo.mobile.client.android.yodel.utils.PostDataLoader;
36 | import com.yahoo.mobile.client.android.yodel.ui.widgets.adapters.PostDetailPagerAdapter;
37 | import com.google.gson.Gson;
38 | import com.tumblr.jumblr.types.Photo;
39 | import com.tumblr.jumblr.types.Post;
40 |
41 | import java.util.List;
42 |
43 | /**
44 | * @author ugo
45 | */
46 | public class PostDetailActivity extends ActionBarActivity
47 | implements LoaderManager.LoaderCallbacks>, PostDetailFragment.Callback {
48 |
49 | private ViewPager mPager;
50 | private PostDetailPagerAdapter mDetailPageAdapter;
51 | private ProgressDialog mProgressDialog;
52 |
53 | private int mCurrentIndexPosition;
54 | private ViewPager.OnPageChangeListener mPageChangeListener =
55 | new ViewPager.OnPageChangeListener() {
56 | @Override
57 | public void onPageScrolled(int position, float positionOffset,
58 | int positionOffsetPixels) { }
59 |
60 | @Override
61 | public void onPageSelected(int position) {
62 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_CAR_CONTENT_SWIPE, null, false);
63 | }
64 |
65 | @Override
66 | public void onPageScrollStateChanged(int state) { }
67 | };
68 |
69 | final static String EXTRA_CURRENT_PAGE_INDEX = "com.yahoo.mdc.extra.currentindex";
70 | final static String EXTRA_SINGLE_POST = "com.yahoo.mdc.extra.singlepost";
71 | final static String EXTRA_SINGLE_POST_TYPE = "com.yahoo.mdc.extra.singleposttype";
72 | private final static String STATE_CURRENT_PAGE_INDEX = "com.yahoo.mdc.state.currentindex";
73 | private final static int LOADER_ID_LOAD_POSTS = 0x1;
74 |
75 | @Override
76 | protected void onCreate(Bundle savedInstanceState) {
77 | super.onCreate(savedInstanceState);
78 | setContentView(R.layout.activity_post_detail);
79 |
80 | Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_actionbar);
81 | setSupportActionBar(toolbar);
82 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
83 | mPager = (ViewPager)findViewById(R.id.post_view_pager);
84 | mPager.setPageTransformer(true, new ParallaxPageTransformer(new int[] { R.id.post_image }));
85 | mPager.setOnPageChangeListener(mPageChangeListener);
86 |
87 | if (savedInstanceState != null) {
88 | mCurrentIndexPosition = savedInstanceState.getInt(STATE_CURRENT_PAGE_INDEX);
89 | }
90 | else {
91 |
92 | if (getIntent().hasExtra(EXTRA_SINGLE_POST)) {
93 | String postType = getIntent().getStringExtra(EXTRA_SINGLE_POST_TYPE);
94 | String postJsonString = getIntent().getStringExtra(EXTRA_SINGLE_POST);
95 | Post post;
96 | post = TumblrFeedManager.deserializePostJson(postType, postJsonString);
97 | mPager.setVisibility(View.GONE);
98 | findViewById(R.id.content_frame).setVisibility(View.VISIBLE);
99 | getSupportFragmentManager().beginTransaction()
100 | .replace(R.id.content_frame, PostDetailFragment.newInstance(post))
101 | .commit();
102 | } else {
103 | // We want to load multiple posts in the viewpager
104 | mDetailPageAdapter = new PostDetailPagerAdapter(this, getSupportFragmentManager());
105 | mPager.setAdapter(mDetailPageAdapter);
106 |
107 | mCurrentIndexPosition = getIntent().getIntExtra(EXTRA_CURRENT_PAGE_INDEX, 0);
108 |
109 | mProgressDialog = new ProgressDialog(PostDetailActivity.this);
110 | mProgressDialog.setTitle(R.string.fetching_posts);
111 | mProgressDialog.setIndeterminate(false);
112 |
113 | getSupportLoaderManager().initLoader(LOADER_ID_LOAD_POSTS, null, this);
114 | }
115 | }
116 | }
117 |
118 | @Override
119 | protected void onStart() {
120 | super.onStart();
121 |
122 | // Uncomment the line below if targeting any API level less than API 14
123 | // FlurryAgent.onStartSession(this);
124 | }
125 |
126 | @Override
127 | protected void onStop() {
128 | super.onStop();
129 |
130 | // Uncomment the line below if targeting any API level less than API 14
131 | // FlurryAgent.onEndSession(this);
132 | }
133 |
134 | @Override
135 | protected void onDestroy() {
136 | super.onDestroy();
137 |
138 | mDetailPageAdapter.destroy();
139 | }
140 |
141 | @Override
142 | public boolean onOptionsItemSelected(MenuItem item) {
143 | switch (item.getItemId()) {
144 | // Respond to the action bar's Up/Home button
145 | case android.R.id.home:
146 | if (getIntent().hasExtra(EXTRA_SINGLE_POST)) {
147 | /*
148 | This activity was not started from the declared parent
149 | Do not go up to the declared parent.
150 | */
151 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
152 | finishAfterTransition();
153 | } else {
154 | finish();
155 | }
156 | return true;
157 | }
158 |
159 | }
160 | return super.onOptionsItemSelected(item);
161 | }
162 |
163 | @Override
164 | public Loader> onCreateLoader(int id, Bundle args) {
165 | switch (id) {
166 | case LOADER_ID_LOAD_POSTS:
167 | if (!mProgressDialog.isShowing()) {
168 | mProgressDialog.show();
169 | }
170 | return new PostDataLoader(this);
171 | }
172 | return null;
173 | }
174 |
175 | @Override
176 | public void onLoadFinished(Loader> loader, List data) {
177 | switch (loader.getId()) {
178 | case LOADER_ID_LOAD_POSTS:
179 | if (mProgressDialog.isShowing()) {
180 | mProgressDialog.dismiss();
181 | }
182 |
183 | mDetailPageAdapter.setBlogPosts(data);
184 | mPager.setCurrentItem(mCurrentIndexPosition, false);
185 | break;
186 | }
187 | }
188 |
189 | @Override
190 | public void onLoaderReset(Loader> loader) {
191 | if (loader.getId() == LOADER_ID_LOAD_POSTS) {
192 | mDetailPageAdapter.setBlogPosts(null);
193 | }
194 | }
195 |
196 | @Override
197 | public void onPostImagesSelected(List imagesToShow, View clickedImageView) {
198 | Intent intent = new Intent(this, ImageGalleryActivity.class);
199 | // Because Photo objects are not parcelable, we serialize to JSON to pass between activities
200 | String photoListJson = new Gson().toJson(imagesToShow);
201 | intent.putExtra(ImageGalleryActivity.EXTRA_PHOTO_LIST, photoListJson);
202 |
203 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
204 | ActivityOptions options = ActivityOptions
205 | .makeSceneTransitionAnimation(this, clickedImageView, "post_image");
206 | startActivity(intent, options.toBundle());
207 | } else {
208 | startActivity(intent);
209 | }
210 | }
211 |
212 | /**
213 | * Page transformer that applies a parallax effect to specified views in a ViewPager.
214 | *
215 | */
216 | class ParallaxPageTransformer implements ViewPager.PageTransformer {
217 | private static final float PARALLAX_COEFFICIENT = -0.3f;
218 | private final int[] PARALLAX_LAYERS;
219 |
220 | ParallaxPageTransformer(int[] parallaxLayers) {
221 | this.PARALLAX_LAYERS = parallaxLayers;
222 | }
223 |
224 | @Override
225 | public void transformPage(View page, float position) {
226 | if (PARALLAX_LAYERS != null) {
227 | float coefficient = page.getWidth() * PARALLAX_COEFFICIENT;
228 |
229 | for (int viewId : PARALLAX_LAYERS) {
230 | View v = page.findViewById(viewId);
231 | if (v != null) {
232 | v.setTranslationX(coefficient * position);
233 | }
234 | }
235 | }
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/PostDetailFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 | import android.support.annotation.Nullable;
22 | import android.support.v4.app.Fragment;
23 | import android.text.TextUtils;
24 | import android.view.LayoutInflater;
25 | import android.view.MenuItem;
26 | import android.view.View;
27 | import android.view.ViewGroup;
28 | import android.widget.ImageButton;
29 | import android.widget.ImageView;
30 | import android.widget.TextView;
31 |
32 | import com.flurry.android.ads.FlurryAdNative;
33 | import com.flurry.android.ads.FlurryAdNativeAsset;
34 | import com.yahoo.mobile.client.android.yodel.R;
35 | import com.yahoo.mobile.client.android.yodel.feed.TumblrFeedManager;
36 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
37 | import com.yahoo.mobile.client.android.yodel.utils.DateTimeUtil;
38 | import com.yahoo.mobile.client.android.yodel.utils.ImageLoader;
39 | import com.yahoo.mobile.client.android.yodel.ui.widgets.SearchToolButton;
40 | import com.tumblr.jumblr.types.LinkPost;
41 | import com.tumblr.jumblr.types.Photo;
42 | import com.tumblr.jumblr.types.PhotoPost;
43 | import com.tumblr.jumblr.types.Post;
44 | import com.tumblr.jumblr.types.QuotePost;
45 | import com.tumblr.jumblr.types.TextPost;
46 | import com.tumblr.jumblr.types.VideoPost;
47 |
48 | import java.util.HashMap;
49 | import java.util.List;
50 |
51 | /**
52 | * @author ugo
53 | */
54 | public class PostDetailFragment extends Fragment {
55 |
56 | private Post mPost;
57 | private FlurryAdNative mAdNative;
58 | private ImageLoader mImageLoader;
59 | private Callback mCallbackHandler;
60 |
61 | private TextView mPublisherTextView;
62 | private TextView mTitleTextView;
63 | private TextView mBodyTextView;
64 | private TextView mTagsTextView;
65 | private TextView mDateTextView;
66 | private ImageView mSponsoredImage;
67 | private ImageView mPostImageView;
68 | private ImageView mImagePlayButton;
69 | private SearchToolButton mSearchToolButton;
70 | private View mMediaContainer;
71 | private View mDividerLine;
72 |
73 | public static final String AD_ASSET_HEADLINE = "headline";
74 | public static final String AD_ASSET_SUMMARY = "summary";
75 | public static final String AD_ASSET_SOURCE = "source";
76 | public static final String AD_ASSET_SEC_HQ_BRANDING_LOGO = "secHqBrandingLogo";
77 | public static final String AD_ASSET_SEC_HQ_IMAGE = "secHqImage";
78 |
79 | public static PostDetailFragment newInstance(Post post) {
80 | PostDetailFragment fragment = new PostDetailFragment();
81 | fragment.mPost = post;
82 | return fragment;
83 | }
84 |
85 | public static PostDetailFragment newInstance(FlurryAdNative adNative) {
86 | PostDetailFragment fragment = new PostDetailFragment();
87 | fragment.mAdNative = adNative;
88 | return fragment;
89 | }
90 |
91 | @Override
92 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
93 | Bundle savedInstanceState) {
94 | View rootView = inflater.inflate(R.layout.fragment_post_detail, container, false);
95 |
96 | mPublisherTextView = (TextView)rootView.findViewById(R.id.post_publisher);
97 | mTitleTextView = (TextView)rootView.findViewById(R.id.post_title);
98 | mBodyTextView = (TextView)rootView.findViewById(R.id.post_body);
99 | mTagsTextView = (TextView)rootView.findViewById(R.id.post_tags);
100 | mDateTextView = (TextView)rootView.findViewById(R.id.date_text);
101 | mSponsoredImage = (ImageView)rootView.findViewById(R.id.sponsored_image);
102 | mPostImageView = (ImageView)rootView.findViewById(R.id.post_image);
103 | mImagePlayButton = (ImageButton)rootView.findViewById(R.id.image_play_button);
104 | mSearchToolButton = (SearchToolButton)rootView.findViewById(R.id.search_tool_button);
105 | mMediaContainer = rootView.findViewById(R.id.media_container);
106 | mDividerLine = rootView.findViewById(R.id.divider_line);
107 | if (mAdNative != null) {
108 | mAdNative.setTrackingView(rootView);
109 | }
110 |
111 | return rootView;
112 | }
113 |
114 | @Override
115 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
116 | super.onActivityCreated(savedInstanceState);
117 |
118 | mImageLoader = ImageLoader.getInstance();
119 | if (mPost != null) {
120 | loadData();
121 | } else if (mAdNative != null) {
122 | loadAd();
123 | }
124 | }
125 |
126 | @Override
127 | public void onAttach(Activity activity) {
128 | super.onAttach(activity);
129 |
130 | if (!(activity instanceof Callback)) {
131 | throw new IllegalStateException("Activity "
132 | + activity.getClass().getSimpleName()
133 | + " must implement PostDetailFragment.Callbacks");
134 | } else {
135 | mCallbackHandler = (Callback)activity;
136 | }
137 | }
138 |
139 | @Override
140 | public boolean onOptionsItemSelected(MenuItem item) {
141 | switch (item.getItemId()) {
142 | // Respond to the action bar's Up/Home button
143 | case android.R.id.home:
144 | // Log the event
145 | HashMap eventParams = new HashMap<>(2);
146 | eventParams.put(
147 | AnalyticsHelper.PARAM_ARTICLE_ORIGIN, mPost.getBlogName());
148 | eventParams.put(AnalyticsHelper.PARAM_ARTICLE_TYPE, mPost.getType());
149 | AnalyticsHelper.logEvent(
150 | AnalyticsHelper.EVENT_CAR_MOREIMG_CLICK, eventParams, false);
151 |
152 | }
153 | return super.onOptionsItemSelected(item);
154 | }
155 |
156 | private void loadAd() {
157 | if (mAdNative != null && mAdNative.isReady()) {
158 | mAdNative.getAsset(AD_ASSET_SOURCE).loadAssetIntoView(mPublisherTextView);
159 | mAdNative.getAsset(AD_ASSET_HEADLINE).loadAssetIntoView(mTitleTextView);
160 | mAdNative.getAsset(AD_ASSET_SUMMARY).loadAssetIntoView(mBodyTextView);
161 |
162 | FlurryAdNativeAsset adNativeAsset = mAdNative.getAsset(AD_ASSET_SEC_HQ_BRANDING_LOGO);
163 | mImageLoader.displayImage(adNativeAsset.getValue(), mSponsoredImage);
164 | adNativeAsset = mAdNative.getAsset(AD_ASSET_SEC_HQ_IMAGE);
165 | mImageLoader.displayImage(adNativeAsset.getValue(), mPostImageView);
166 | /*
167 | The above lines could be replaced with the following. In that case, you would let the
168 | Flurry SDK handle ad image management for your app. However, this would prevent on-device
169 | caching.
170 |
171 | mAdNative.getAsset(AD_ASSET_SEC_HQ_BRANDING_LOGO).loadAssetIntoView(mSponsoredImage);
172 | mAdNative.getAsset(AD_ASSET_SEC_HQ_IMAGE).loadAssetIntoView(mPostImageView);
173 | */
174 |
175 | mDateTextView.setText(R.string.sponsored_text);
176 | mPublisherTextView.setTextColor(getResources().getColor(R.color.sponsored_text_color));
177 | mDividerLine.setBackgroundResource(R.color.sponsored_text_color);
178 |
179 | mSearchToolButton.setVisibility(View.GONE);
180 | }
181 | }
182 |
183 | private void loadData() {
184 | if (mPost != null) {
185 | String type = mPost.getType();
186 | String blogName, title, body, photoUrl, postDate;
187 | int photoCount;
188 | blogName = mPost.getBlogName();
189 | title = body = photoUrl = null;
190 |
191 | StringBuilder tags = new StringBuilder();
192 | for (String tag : mPost.getTags()) {
193 | tags.append("#").append(tag).append(" ");
194 | }
195 | postDate = DateTimeUtil.getFriendlyDateString(mPost.getTimestamp() * 1000, getActivity());
196 |
197 | switch (type) {
198 | case TumblrFeedManager.POST_TYPE_TEXT:
199 | title = ((TextPost)mPost).getTitle();
200 | body = ((TextPost)mPost).getBody();
201 | break;
202 | case TumblrFeedManager.POST_TYPE_QUOTE:
203 | body = ((QuotePost)mPost).getText() + "\n - " + ((QuotePost)mPost).getSource();
204 | break;
205 | case TumblrFeedManager.POST_TYPE_PHOTO:
206 | body = ((PhotoPost)mPost).getCaption();
207 | // It can't be a photo post without photos. Either way...
208 | photoCount = ((PhotoPost)mPost).getPhotos().size();
209 | if (photoCount > 0) {
210 | photoUrl = ((PhotoPost) mPost).getPhotos().get(0).getOriginalSize().getUrl();
211 | }
212 | break;
213 | case TumblrFeedManager.POST_TYPE_VIDEO:
214 | body = ((VideoPost)mPost).getCaption();
215 | photoUrl = ((VideoPost)mPost).getThumbnailUrl();
216 | break;
217 | case TumblrFeedManager.POST_TYPE_LINK:
218 | title = ((LinkPost)mPost).getTitle();
219 | body = ((LinkPost)mPost).getDescription() + "\n" + ((LinkPost)mPost).getLinkUrl();
220 | break;
221 | }
222 |
223 | mPublisherTextView.setText(blogName);
224 | if (title != null) {
225 | mTitleTextView.setVisibility(View.VISIBLE);
226 | mTitleTextView.setText(title);
227 | } else {
228 | mTitleTextView.setVisibility(View.GONE);
229 | }
230 | if (body != null) {
231 | mBodyTextView.setVisibility(View.VISIBLE);
232 | mBodyTextView.setText(body);
233 | } else {
234 | mBodyTextView.setVisibility(View.GONE);
235 | }
236 | if (!TextUtils.isEmpty(tags)) {
237 | mTagsTextView.setVisibility(View.VISIBLE);
238 | mTagsTextView.setText(tags);
239 | }
240 | mDateTextView.setText(postDate);
241 | if (photoUrl != null) {
242 | mMediaContainer.setVisibility(View.VISIBLE);
243 | mPostImageView.setVisibility(View.VISIBLE);
244 | mImagePlayButton.setVisibility(View.VISIBLE);
245 | mImageLoader.displayImage(photoUrl, mPostImageView);
246 | if (type.equals(TumblrFeedManager.POST_TYPE_PHOTO)) {
247 | mImagePlayButton.setImageResource(R.drawable.ic_gallery);
248 | mImagePlayButton.setOnClickListener(new View.OnClickListener() {
249 | @Override
250 | public void onClick(View v) {
251 | HashMap eventParams = new HashMap<>(2);
252 | eventParams.put(
253 | AnalyticsHelper.PARAM_ARTICLE_ORIGIN, mPost.getBlogName());
254 | eventParams.put(AnalyticsHelper.PARAM_ARTICLE_TYPE, mPost.getType());
255 | AnalyticsHelper.logEvent(
256 | AnalyticsHelper.EVENT_CAR_MOREIMG_CLICK, eventParams, false);
257 |
258 | mCallbackHandler.onPostImagesSelected(
259 | ((PhotoPost) mPost).getPhotos(), mPostImageView);
260 |
261 | }
262 | });
263 | } else {
264 | mImagePlayButton.setImageResource(R.drawable.ic_video);
265 | }
266 | } else {
267 | mMediaContainer.setVisibility(View.GONE);
268 | mPostImageView.setVisibility(View.GONE);
269 | mImagePlayButton.setVisibility(View.GONE);
270 | }
271 |
272 | if (mPost.getTags().size() > 0) {
273 | mSearchToolButton.setSearchTerm(mPost.getTags().get(0));
274 | } else {
275 | mSearchToolButton.setVisibility(View.GONE);
276 | }
277 | }
278 | }
279 |
280 | public interface Callback {
281 | void onPostImagesSelected(List photosToShow, View clickedImageView);
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/PostListFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.app.Activity;
20 | import android.app.AlertDialog;
21 | import android.app.ProgressDialog;
22 | import android.content.DialogInterface;
23 | import android.content.Intent;
24 | import android.content.res.TypedArray;
25 | import android.os.Bundle;
26 | import android.support.annotation.Nullable;
27 | import android.support.v4.app.ListFragment;
28 | import android.support.v4.app.LoaderManager;
29 | import android.support.v4.content.Loader;
30 | import android.support.v4.widget.SwipeRefreshLayout;
31 | import android.util.Log;
32 | import android.view.LayoutInflater;
33 | import android.view.View;
34 | import android.view.ViewGroup;
35 | import android.widget.ListView;
36 |
37 | import com.yahoo.mobile.client.android.yodel.R;
38 | import com.yahoo.mobile.client.android.yodel.ui.widgets.adapters.PostSearchListAdapter;
39 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
40 | import com.yahoo.mobile.client.android.yodel.utils.PostDataLoader;
41 | import com.yahoo.mobile.client.android.yodel.ui.widgets.adapters.PostListAdapter;
42 | import com.tumblr.jumblr.types.Post;
43 | import com.yahoo.mobile.client.share.search.ui.activity.SearchActivity;
44 |
45 | import java.util.HashMap;
46 | import java.util.List;
47 |
48 | /**
49 | * The list fragment containing Tumblr blog posts.
50 | */
51 | public class PostListFragment extends ListFragment
52 | implements LoaderManager.LoaderCallbacks> {
53 |
54 | private final static String LOG_TAG = PostListFragment.class.getSimpleName();
55 |
56 | private List mBlogPostList = null;
57 | private PostListAdapter mPostListAdapter = null;
58 | private PostSearchListAdapter mPostSearchListAdapter = null;
59 | private SwipeRefreshLayout mSwipeView = null;
60 | private AlertDialog mAlertDialog = null;
61 | private Callbacks mCallbackHandler;
62 | private String mTagQuery;
63 |
64 | private ProgressDialog mProgressDialog;
65 |
66 | private final static int LOADER_ID_LOAD_RECENT_POSTS = 0x1;
67 | private final static int LOADER_ID_LOAD_POSTS_WITH_TAGS = 0x2;
68 | private final static String EXTRA_TAG_QUERY = "com.yahoo.mobile.sample.extra.tagquery";
69 |
70 | static PostListFragment newInstance() {
71 | return new PostListFragment();
72 | }
73 |
74 | static PostListFragment newInstance(String tagQuery) {
75 | PostListFragment fragment = new PostListFragment();
76 | Bundle bundle = new Bundle();
77 | bundle.putString(EXTRA_TAG_QUERY, tagQuery);
78 | fragment.setArguments(bundle);
79 |
80 | return fragment;
81 | }
82 |
83 | @Override
84 | public void onCreate(Bundle savedInstanceState) {
85 | super.onCreate(savedInstanceState);
86 |
87 | if (savedInstanceState == null) {
88 | Bundle args = getArguments();
89 | if (args != null) {
90 | mTagQuery = args.getString(EXTRA_TAG_QUERY);
91 | }
92 | }
93 | }
94 |
95 | @Override
96 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
97 | Bundle savedInstanceState) {
98 | View rootView = inflater.inflate(R.layout.fragment_main, container, false);
99 |
100 | mSwipeView = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe);
101 |
102 | mSwipeView.setColorSchemeResources(R.color.accent_color, R.color.red_transparent,
103 | R.color.purple_transparent, R.color.y_blue);
104 | mSwipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
105 | @Override
106 | public void onRefresh() {
107 | // Log a timed event
108 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_STREAM_PULL_REFRESH, null, true);
109 |
110 | mSwipeView.setRefreshing(true);
111 | refreshPosts();
112 | }
113 | });
114 | // Create a reusable progress dialog
115 | createProgressDialog();
116 |
117 | return rootView;
118 | }
119 |
120 | @Override
121 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
122 | super.onActivityCreated(savedInstanceState);
123 |
124 | if (mTagQuery == null) {
125 | mPostListAdapter = new PostListAdapter(getActivity());
126 | setListAdapter(mPostListAdapter);
127 | } else {
128 | View footer = getActivity().getLayoutInflater()
129 | .inflate(R.layout.view_more_web_footer, getListView(), false);
130 | mPostSearchListAdapter = new PostSearchListAdapter(getActivity());
131 | getListView().addFooterView(footer);
132 | setListAdapter(mPostSearchListAdapter);
133 | }
134 |
135 | // If blog post list wasn't persisted during config change or is just empty...
136 | if (mBlogPostList == null || mBlogPostList.size() > 0) {
137 | refreshPosts();
138 | }
139 |
140 | /*
141 | Account for the overlay Toolbar. Warning: makes it hard to re-use this fragment
142 | (which is the whole point of fragments, isn't it?)
143 | */
144 | final TypedArray styledAttributes = getActivity().getTheme().obtainStyledAttributes(
145 | new int[] { android.R.attr.actionBarSize });
146 | int actionBarSize = (int) styledAttributes.getDimension(0, 0);
147 | styledAttributes.recycle();
148 | int mediumViewMargin = getResources().getDimensionPixelSize(R.dimen.view_margin_medium);
149 | int lvTopPadding = mediumViewMargin +
150 | actionBarSize;
151 | getListView().setPadding(0, lvTopPadding, 0, mediumViewMargin);
152 | mSwipeView.setProgressViewOffset(true, mediumViewMargin, lvTopPadding);
153 | }
154 |
155 | @Override
156 | public void onAttach(Activity activity) {
157 | super.onAttach(activity);
158 |
159 | if (!(activity instanceof Callbacks)) {
160 | throw new IllegalStateException("Activity "
161 | + activity.getClass().getSimpleName()
162 | + " must implement PostListFragment.Callbacks");
163 | } else {
164 | mCallbackHandler = (Callbacks)activity;
165 | }
166 | }
167 |
168 | @Override
169 | public void onDetach() {
170 | super.onDetach();
171 | mCallbackHandler = null;
172 | }
173 |
174 | @Override
175 | public void onPause() {
176 | super.onPause();
177 |
178 | if (mProgressDialog != null && mProgressDialog.isShowing()) {
179 | mProgressDialog.dismiss();
180 | }
181 | }
182 |
183 | @Override
184 | public void onDestroyView() {
185 | super.onDestroyView();
186 |
187 | setListAdapter(null);
188 |
189 | if (mPostListAdapter != null) {
190 | mPostListAdapter.destroy();
191 | mPostListAdapter = null;
192 | }
193 |
194 | if (mAlertDialog != null && mAlertDialog.isShowing()) {
195 | mAlertDialog.dismiss();
196 | }
197 | }
198 |
199 | @Override
200 | public void onListItemClick(ListView listView, View clickedView, int position, long id) {
201 | if (mCallbackHandler != null) {
202 | if (mTagQuery == null) {
203 | mCallbackHandler.onPostSelected(
204 | (Post) mPostListAdapter.getItem(position), position, clickedView);
205 | } else {
206 | if (position + 1 > mPostSearchListAdapter.getCount()) {
207 | // The search footer was clicked. Log the event and open the Search SDK
208 | HashMap eventParam = new HashMap<>(1);
209 | eventParam.put(AnalyticsHelper.PARAM_SEARCH_TERM, mTagQuery);
210 | AnalyticsHelper.logEvent(
211 | AnalyticsHelper.EVENT_SEARCH_MOREONWEB_CLICK, eventParam, false);
212 |
213 | Intent i = new Intent(getActivity(), SearchActivity.class);
214 | i.putExtra(SearchActivity.QUERY_STRING, mTagQuery);
215 | i.putExtra(SearchActivity.HEADER_RESOURCE_KEY, R.layout.view_ysearch_header);
216 | getActivity().startActivity(i);
217 | } else {
218 | mCallbackHandler.onPostSelected(
219 | (Post) mPostSearchListAdapter.getItem(position), position, clickedView);
220 | }
221 | }
222 | }
223 | }
224 |
225 | @Override
226 | public Loader> onCreateLoader(int id, Bundle args) {
227 | showProgressDialog();
228 | switch (id) {
229 | case LOADER_ID_LOAD_RECENT_POSTS:
230 | return new PostDataLoader(getActivity());
231 | case LOADER_ID_LOAD_POSTS_WITH_TAGS:
232 | return new PostDataLoader(getActivity(), mTagQuery);
233 | }
234 | return null;
235 | }
236 |
237 | @Override
238 | public void onLoadFinished(Loader> loader, List data) {
239 | hideProgressDialog();
240 | mBlogPostList = data;
241 | boolean contentLoaded = false;
242 |
243 | switch (loader.getId()) {
244 | case LOADER_ID_LOAD_RECENT_POSTS:
245 | if (data != null && data.size() > 0) {
246 | contentLoaded = true;
247 | mPostListAdapter.setBlogPosts(mBlogPostList);
248 | } else if (data == null) {
249 | // Network is unavailable
250 | showAlertDialog();
251 | }
252 | break;
253 | case LOADER_ID_LOAD_POSTS_WITH_TAGS:
254 | if (data != null && data.size() > 0) {
255 | contentLoaded = true;
256 | mPostSearchListAdapter.setBlogPosts(mBlogPostList);
257 | } else if (data == null) {
258 | // Network is unavailable
259 | showAlertDialog();
260 | }
261 | break;
262 | }
263 | if (mSwipeView.isRefreshing()) {
264 | HashMap eventParams = new HashMap<>(1);
265 | eventParams.put(AnalyticsHelper.PARAM_CONTENT_LOADED, String.valueOf(contentLoaded));
266 | AnalyticsHelper.endTimedEvent(AnalyticsHelper.EVENT_STREAM_PULL_REFRESH, eventParams);
267 | mSwipeView.setRefreshing(false);
268 | }
269 | }
270 |
271 | @Override
272 | public void onLoaderReset(Loader> loader) {
273 | switch (loader.getId()) {
274 | case LOADER_ID_LOAD_RECENT_POSTS:
275 | if (mPostListAdapter != null) {
276 | mPostListAdapter.setBlogPosts(null);
277 | }
278 | break;
279 | case LOADER_ID_LOAD_POSTS_WITH_TAGS:
280 | if (mPostSearchListAdapter != null) {
281 | mPostSearchListAdapter.setBlogPosts(null);
282 | }
283 | }
284 | }
285 |
286 | private void refreshPosts() {
287 | Log.d(LOG_TAG, "Refreshing post list...");
288 | if (mTagQuery == null) {
289 | getActivity().getSupportLoaderManager()
290 | .restartLoader(LOADER_ID_LOAD_RECENT_POSTS, null, this);
291 | } else {
292 | getActivity().getSupportLoaderManager()
293 | .restartLoader(LOADER_ID_LOAD_POSTS_WITH_TAGS, null, this);
294 | }
295 | }
296 |
297 | public void createProgressDialog() {
298 | mProgressDialog = new ProgressDialog(getActivity());
299 | mProgressDialog.setTitle(R.string.fetching_posts);
300 | mProgressDialog.setIndeterminate(false);
301 | mProgressDialog.setCancelable(false);
302 | }
303 |
304 | public void showProgressDialog(){
305 | if (mProgressDialog != null && !mProgressDialog.isShowing()) {
306 | mProgressDialog.show();
307 | }
308 | }
309 |
310 | public void hideProgressDialog(){
311 | if (mProgressDialog != null && mProgressDialog.isShowing()) {
312 | mProgressDialog.hide();
313 | }
314 | }
315 |
316 | public void showAlertDialog(){
317 | MainActivity activity = ((MainActivity) getActivity());
318 | if (activity == null) {
319 | return;
320 | }
321 | AlertDialog.Builder alertDialog = new AlertDialog.Builder(activity);
322 | alertDialog.setMessage(R.string.network_not_available);
323 | alertDialog.setCancelable(true);
324 | alertDialog.setPositiveButton(R.string.retry_button,
325 | new DialogInterface.OnClickListener() {
326 | public void onClick(DialogInterface dialog, int id) {
327 | refreshPosts();
328 | dialog.dismiss();
329 | }
330 | });
331 | alertDialog.setNegativeButton(R.string.cancel_button,
332 | new DialogInterface.OnClickListener() {
333 | public void onClick(DialogInterface dialog, int id) {
334 | dialog.dismiss();
335 | }
336 | });
337 |
338 | mAlertDialog = alertDialog.create();
339 | mAlertDialog.show();
340 | }
341 |
342 | public interface Callbacks {
343 | void onPostSelected(Post post, int positionId, View clickedView);
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/PostSearchActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui;
18 |
19 | import android.app.ActivityOptions;
20 | import android.app.SearchManager;
21 | import android.content.Intent;
22 | import android.os.Build;
23 | import android.os.Bundle;
24 | import android.support.v7.app.ActionBarActivity;
25 | import android.support.v7.widget.Toolbar;
26 | import android.text.Html;
27 | import android.view.View;
28 |
29 | import com.google.gson.Gson;
30 | import com.yahoo.mobile.client.android.yodel.R;
31 |
32 | import com.tumblr.jumblr.types.Post;
33 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
34 |
35 | import java.util.HashMap;
36 |
37 | public class PostSearchActivity extends ActionBarActivity implements PostListFragment.Callbacks {
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 |
43 | Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar_actionbar);
44 | setSupportActionBar(toolbar);
45 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
46 |
47 | handleIntent(getIntent());
48 | }
49 |
50 | private void handleIntent(Intent intent) {
51 | if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
52 | String searchQuery = intent.getStringExtra(SearchManager.QUERY);
53 |
54 | HashMap eventParam = new HashMap<>(1);
55 | eventParam.put(AnalyticsHelper.PARAM_SEARCH_TERM, searchQuery);
56 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_SEARCH_STARTED, eventParam, false);
57 |
58 | doTumblrTagSearch(searchQuery);
59 | setTitle(Html.fromHtml(getString(R.string.title_search_query, searchQuery)));
60 | }
61 | }
62 |
63 | private void doTumblrTagSearch(String searchQuery) {
64 | getSupportFragmentManager().beginTransaction()
65 | .replace(R.id.content_frame, PostListFragment.newInstance(searchQuery))
66 | .commit();
67 | }
68 |
69 | @Override
70 | public void onPostSelected(Post post, int positionId, View clickedView) {
71 | // Log the event
72 | HashMap eventParams = new HashMap<>(2);
73 | eventParams.put(AnalyticsHelper.PARAM_ARTICLE_ORIGIN, post.getBlogName());
74 | eventParams.put(AnalyticsHelper.PARAM_ARTICLE_TYPE, post.getType());
75 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_SEARCH_RESULT_CLICK, eventParams, false);
76 |
77 | /*
78 | To avoid a StackOverflowError from a circular reference during serialization, set the
79 | client of the post to null.
80 | */
81 | post.setClient(null);
82 |
83 | String postJson = new Gson().toJson(post);
84 | Intent intent = new Intent(this, PostDetailActivity.class);
85 | intent.putExtra(PostDetailActivity.EXTRA_SINGLE_POST, postJson);
86 | intent.putExtra(PostDetailActivity.EXTRA_SINGLE_POST_TYPE, post.getType());
87 |
88 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
89 | View sharedImageElement = clickedView.findViewById(R.id.post_image);
90 | ActivityOptions options = ActivityOptions
91 | .makeSceneTransitionAnimation(this, sharedImageElement, "post_image");
92 | startActivity(intent, options.toBundle());
93 | } else {
94 | startActivity(intent);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/AspectRatioImageView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets;
18 |
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.util.AttributeSet;
22 | import android.widget.ImageView;
23 |
24 | import com.yahoo.mobile.client.android.yodel.R;
25 |
26 |
27 | /**
28 | * ImageView that respects a proportional relationship between its width and its height, regardless
29 | * of drawable size and regardless of screen size.
30 | */
31 | public class AspectRatioImageView extends ImageView {
32 |
33 | private float mAspectRatioWidth;
34 | private float mAspectRationHeight;
35 | private boolean mWidthAsBase;
36 |
37 | public AspectRatioImageView(Context context, AttributeSet attrs) {
38 | super(context, attrs);
39 |
40 | TypedArray a = context.getTheme().obtainStyledAttributes(
41 | attrs, R.styleable.AspectRatioImageView,
42 | 0, 0);
43 |
44 | if (a != null) {
45 | // Defaults to a 4:3 ratio
46 | setAspectRatioWidth(a.getInt(R.styleable.AspectRatioImageView_aspectRatioWidth, 4));
47 | setAspectRationHeight(a.getInt(R.styleable.AspectRatioImageView_aspectRatioHeight, 3));
48 |
49 | setWidthAsBase(a.getBoolean(R.styleable.AspectRatioImageView_widthAsBase, true));
50 |
51 | a.recycle();
52 | }
53 | }
54 |
55 | @Override
56 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
57 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
58 |
59 | if (isWidthAsBase()) {
60 | int newHeight = (int)(getMeasuredWidth() * getAspectRationHeight() / getAspectRatioWidth());
61 | setMeasuredDimension(getMeasuredWidth(), newHeight);
62 | } else {
63 | int newWidth = (int)(getMeasuredHeight() * getAspectRatioWidth() / getAspectRationHeight());
64 | setMeasuredDimension(newWidth, getMeasuredHeight());
65 | }
66 | }
67 |
68 | public float getAspectRatioWidth() {
69 | return mAspectRatioWidth;
70 | }
71 |
72 | public void setAspectRatioWidth(int aspectRatioWidth) {
73 | mAspectRatioWidth = aspectRatioWidth;
74 | }
75 |
76 | public float getAspectRationHeight() {
77 | return mAspectRationHeight;
78 | }
79 |
80 | public void setAspectRationHeight(int aspectRationHeight) {
81 | mAspectRationHeight = aspectRationHeight;
82 | }
83 |
84 | public boolean isWidthAsBase() {
85 | return mWidthAsBase;
86 | }
87 |
88 | public void setWidthAsBase(boolean widthAsBase) {
89 | mWidthAsBase = widthAsBase;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/CaptionViewPagerIndicator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.widget.LinearLayout;
24 | import android.widget.TextView;
25 |
26 | import com.yahoo.mobile.client.android.yodel.R;
27 |
28 | public class CaptionViewPagerIndicator extends LinearLayout {
29 |
30 | private TextView mCaptionTextView;
31 | private TextView mPageIndicatorTextView;
32 |
33 | public CaptionViewPagerIndicator(Context context) {
34 | this(context, null);
35 | }
36 |
37 | public CaptionViewPagerIndicator(Context context, AttributeSet attrs) {
38 | this(context, attrs, 0);
39 | }
40 |
41 |
42 | public CaptionViewPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 |
45 | LayoutInflater layoutInflater = (LayoutInflater)context
46 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
47 | View root = layoutInflater.inflate(R.layout.view_caption_pager_indicator, this);
48 | mCaptionTextView = (TextView)root.findViewById(R.id.image_caption_text);
49 | mPageIndicatorTextView = (TextView)root.findViewById(R.id.page_indicator_text);
50 | }
51 |
52 | public void setCurrentItem(int itemIndex, int total) {
53 | mPageIndicatorTextView.setText(getResources().getString(R.string.view_pager_indicator,
54 | itemIndex + 1, total));
55 | }
56 |
57 | public void setCaption(CharSequence caption) {
58 | mCaptionTextView.setText(caption);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/CustomSearchViewHolder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.View;
22 | import android.widget.EditText;
23 | import android.widget.LinearLayout;
24 |
25 | import com.yahoo.mobile.client.android.yodel.R;
26 | import com.yahoo.mobile.client.share.search.interfaces.ISearchController;
27 | import com.yahoo.mobile.client.share.search.interfaces.ISearchViewHolder;
28 |
29 | /**
30 | * A custom search view holder to be used as header in Yahoo Search SDK.
31 | * See Yahoo Search SDK documentation for more.
32 | */
33 | public class CustomSearchViewHolder extends LinearLayout implements ISearchViewHolder {
34 |
35 | public CustomSearchViewHolder(Context context) {
36 | super(context);
37 | }
38 |
39 | public CustomSearchViewHolder(Context context, AttributeSet attrs) {
40 | super(context, attrs);
41 | }
42 |
43 | public CustomSearchViewHolder(Context context, AttributeSet attrs, int defStyleAttr) {
44 | super(context, attrs, defStyleAttr);
45 | }
46 |
47 | @Override
48 | public void setSearchController(ISearchController iSearchController) {
49 |
50 | }
51 |
52 | @Override
53 | public EditText getSearchEditText() {
54 | return (EditText)findViewById(R.id.search_bar_edit_text);
55 | }
56 |
57 | @Override
58 | public View getVoiceSearchButton() {
59 | return null;
60 | }
61 |
62 | @Override
63 | public View getClearTextButton() {
64 | return findViewById(R.id.search_bar_clear_icon);
65 | }
66 |
67 | @Override
68 | public int getSearchViewHeightOffset() {
69 | return 0;
70 | }
71 |
72 | @Override
73 | public void onVoiceSearchAvailabilityChanged(boolean b) {
74 |
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/SearchToolButton.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets;
18 |
19 | import android.annotation.TargetApi;
20 | import android.content.Context;
21 | import android.content.Intent;
22 | import android.os.Build;
23 | import android.text.Html;
24 | import android.util.AttributeSet;
25 | import android.view.LayoutInflater;
26 | import android.view.View;
27 | import android.widget.LinearLayout;
28 | import android.widget.TextView;
29 |
30 | import com.yahoo.mobile.client.android.yodel.R;
31 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
32 | import com.yahoo.mobile.client.share.search.ui.activity.SearchActivity;
33 |
34 | import java.util.HashMap;
35 |
36 | public class SearchToolButton extends LinearLayout implements View.OnClickListener {
37 |
38 | private TextView mSearchTermTextView;
39 | private View mContainer;
40 | private CharSequence mSearchTerm;
41 |
42 | public SearchToolButton(Context context, AttributeSet attrs) {
43 | super(context, attrs);
44 |
45 | LayoutInflater layoutInflater = (LayoutInflater)context
46 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
47 | View root = layoutInflater.inflate(R.layout.view_search_tool_button, this);
48 | mSearchTermTextView = (TextView)root.findViewById(R.id.search_term_text);
49 | mContainer = root.findViewById(R.id.search_button_container);
50 |
51 | this.setOnClickListener(this);
52 | }
53 |
54 | public void setSearchTerm(CharSequence searchTerm) {
55 | mSearchTerm = searchTerm;
56 | mSearchTermTextView.setText(Html.fromHtml(getContext().getResources()
57 | .getString(R.string.search_learn_more_prompt, searchTerm)));
58 | }
59 |
60 | public CharSequence getSearchTerm() {
61 | return mSearchTerm;
62 | }
63 |
64 | @Override
65 | public void onClick(View v) {
66 | HashMap eventParam = new HashMap<>(1);
67 | eventParam.put(AnalyticsHelper.PARAM_SEARCH_TERM, getSearchTerm().toString());
68 | AnalyticsHelper.logEvent(AnalyticsHelper.EVENT_CAR_LEARNMORE_CLICK, eventParam, false);
69 |
70 | Intent i = new Intent(getContext(),
71 | SearchActivity.class);
72 | i.putExtra(SearchActivity.QUERY_STRING, getSearchTerm());
73 | i.putExtra(SearchActivity.HEADER_RESOURCE_KEY, R.layout.view_ysearch_header);
74 | getContext().startActivity(i);
75 | }
76 |
77 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
78 | @Override
79 | public void drawableHotspotChanged(float x, float y) {
80 | super.drawableHotspotChanged(x, y);
81 |
82 | if (mContainer.getBackground() != null) {
83 | mContainer.getBackground().setHotspot(x, y);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/adapters/BaseAdAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets.adapters;
18 |
19 | import android.content.Context;
20 | import android.view.View;
21 |
22 | import com.flurry.android.ads.FlurryAdNative;
23 | import com.flurry.android.ads.FlurryAdNativeAsset;
24 | import com.yahoo.mobile.client.android.yodel.NativeAdFetcher;
25 |
26 | import java.util.List;
27 |
28 | /**
29 | * Adapter that has common functionality for any adapters that need to show ads in-between
30 | * other data.
31 | */
32 | abstract class BaseAdAdapter implements NativeAdFetcher.AdNativeListener {
33 |
34 | private final static int DEFAULT_NO_OF_DATA_BETWEEN_ADS = 3;
35 | private int mNoOfDataBetweenAds;
36 | private volatile List mData = null;
37 | private NativeAdFetcher mAdFetcher;
38 |
39 | protected BaseAdAdapter(Context context) {
40 | setNoOfDataBetweenAds(DEFAULT_NO_OF_DATA_BETWEEN_ADS); // Default
41 | this.mAdFetcher = new NativeAdFetcher();
42 | mAdFetcher.addListener(this);
43 | // Start prefetching ads
44 | mAdFetcher.prefetchAds(context);
45 | }
46 |
47 |
48 | @Override
49 | public abstract void onAdCountChanged();
50 |
51 | /**
52 | * Gets the count of all data, including interspersed ads.
53 | *
54 | * If data size is 10 and an ad is to be showed after every 5 items, this method
55 | * will return 12.
56 | *
57 | * @see BaseAdAdapter#setNoOfDataBetweenAds(int)
58 | * @see BaseAdAdapter#getNoOfDataBetweenAds()
59 | * @return the total number of items this adapter can show, including ads.
60 | */
61 | protected int getCount() {
62 | if (mData != null) {
63 | /*
64 | No of currently fetched ads, as long as it isn't more than no of max ads that can
65 | fit dataset.
66 | */
67 | int noOfAds = Math.min(mAdFetcher.getFetchedAdsCount(),
68 | mData.size() / getNoOfDataBetweenAds());
69 | return mData.size() > 0 ? mData.size() + noOfAds : 0;
70 | } else {
71 | return 0;
72 | }
73 | }
74 |
75 | /**
76 | * Gets the item in a given position in the dataset. If an ad is to be returned,
77 | * a {@link com.flurry.android.ads.FlurryAdNative} object is returned.
78 | *
79 | * @param position the adapter position
80 | * @return the object or ad contained in this adapter position
81 | */
82 | protected Object getItem(int position) {
83 | if (canShowAdAtPosition(position)) {
84 | return mAdFetcher.getAdForIndex(getAdIndex(position));
85 | } else {
86 | return mData.get(getOriginalContentPosition(position));
87 | }
88 | }
89 |
90 | /**
91 | * Translates an adapter position to an actual position within the underlying dataset.
92 | *
93 | * @param position the adapter position
94 | * @return the original position that the adapter position would have been without ads
95 | */
96 | protected int getOriginalContentPosition(int position) {
97 | int noOfFetchedAds = mAdFetcher.getFetchedAdsCount();
98 | // No of spaces for ads in the dataset, according to ad placement rules
99 | int adSpacesCount = position / (getNoOfDataBetweenAds() + 1);
100 | return position - Math.min(adSpacesCount, noOfFetchedAds);
101 | }
102 |
103 | /**
104 | * Determines if an ad can be shown at the given position. Checks if the position is for
105 | * an ad, using the preconfigured ad positioning rules; and if a native ad object is
106 | * available to place in that position.
107 | *
108 | * @param position the adapter position
109 | * @return true
if ads can
110 | */
111 | protected boolean canShowAdAtPosition(int position) {
112 |
113 | // Is this a valid position for an ad?
114 | boolean isAdPosition = isAdPosition(position);
115 | // Is an ad for this position available?
116 | boolean isAdAvailable = isAdAvailable(position);
117 |
118 | return isAdPosition && isAdAvailable;
119 | }
120 |
121 | /**
122 | * Destroys all currently fetched ads
123 | */
124 | protected void destroy() {
125 | mAdFetcher.destroyAllAds();
126 | }
127 |
128 | /**
129 | * Loads an ad asset into a given view.
130 | *
131 | * @param adNative the {@link com.flurry.android.ads.FlurryAdNative} containing the ad asset
132 | * @param assetName the name of the asset
133 | * @param view the {@link View} to load the asset into
134 | */
135 | protected void loadAdAssetInView(FlurryAdNative adNative, String assetName, View view) {
136 | FlurryAdNativeAsset adNativeAsset = adNative.getAsset(assetName);
137 | if (adNativeAsset != null) {
138 | adNativeAsset.loadAssetIntoView(view);
139 | } else {
140 | view.setVisibility(View.GONE);
141 | }
142 | }
143 |
144 | /**
145 | * Sets the underlying dataset.
146 | *
147 | * @param data the dataset
148 | */
149 | protected void setData(List data) {
150 | mData = data;
151 | }
152 |
153 | /**
154 | * Gets the number of data items to show between ads.
155 | *
156 | * @return the number of data items to show between ads
157 | */
158 | protected int getNoOfDataBetweenAds() {
159 | return mNoOfDataBetweenAds;
160 | }
161 |
162 | /**
163 | * Sets the number of data items to show between ads.
164 | *
165 | * @param noOfDataBetweenAds the number of data items to show between ads
166 | */
167 | protected void setNoOfDataBetweenAds(int noOfDataBetweenAds) {
168 | mNoOfDataBetweenAds = noOfDataBetweenAds;
169 | }
170 |
171 | /**
172 | * Gets the ad index for this adapter position within the list of currently fetched ads.
173 | *
174 | * @param position the adapter position
175 | * @return the index of the ad within the list of fetched ads
176 | */
177 | private int getAdIndex(int position) {
178 | return (position / getNoOfDataBetweenAds()) - 1;
179 | }
180 |
181 | /**
182 | * Checks if adapter position is an ad position.
183 | *
184 | * @param position the adapter position
185 | * @return {@code true} if an ad position, {@code false} otherwise
186 | */
187 | private boolean isAdPosition(int position) {
188 | return (position + 1) % (getNoOfDataBetweenAds() + 1) == 0;
189 | }
190 |
191 | /**
192 | * Checks if an ad is available for this position.
193 | *
194 | * @param position the adapter position
195 | * @return {@code true} if an ad is available, {@code false} otherwise
196 | */
197 | private boolean isAdAvailable(int position) {
198 | int adIndex = getAdIndex(position);
199 | return position >= getNoOfDataBetweenAds()
200 | && adIndex >= 0
201 | && mAdFetcher.getFetchedAdsCount() > adIndex;
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/adapters/GalleryPagerAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets.adapters;
18 |
19 | import android.support.v4.app.Fragment;
20 | import android.support.v4.app.FragmentManager;
21 | import android.support.v4.app.FragmentStatePagerAdapter;
22 |
23 | import com.yahoo.mobile.client.android.yodel.ui.FullImageFragment;
24 | import com.tumblr.jumblr.types.Photo;
25 |
26 | import java.util.List;
27 |
28 | public class GalleryPagerAdapter extends FragmentStatePagerAdapter {
29 |
30 | private List mPhotos;
31 |
32 | public GalleryPagerAdapter(FragmentManager fm, List photos) {
33 | super(fm);
34 |
35 | this.mPhotos = photos;
36 | }
37 |
38 | @Override
39 | public Fragment getItem(int position) {
40 | return FullImageFragment.newInstance(mPhotos.get(position).getOriginalSize().getUrl());
41 | }
42 |
43 | @Override
44 | public int getCount() {
45 | return mPhotos != null ? mPhotos.size() : 0;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/adapters/PostDetailPagerAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets.adapters;
18 |
19 | import android.content.Context;
20 | import android.support.v4.app.Fragment;
21 | import android.support.v4.app.FragmentManager;
22 | import android.support.v4.app.FragmentStatePagerAdapter;
23 |
24 | import com.flurry.android.ads.FlurryAdNative;
25 | import com.yahoo.mobile.client.android.yodel.ui.PostDetailFragment;
26 | import com.tumblr.jumblr.types.Post;
27 |
28 | import java.util.List;
29 |
30 | public class PostDetailPagerAdapter extends FragmentStatePagerAdapter {
31 | private BaseAdAdapter mAdAdapter;
32 |
33 | public PostDetailPagerAdapter(Context context, FragmentManager fm) {
34 | super(fm);
35 | mAdAdapter = new BaseAdAdapter(context) {
36 | @Override
37 | public void onAdCountChanged() {
38 | notifyDataSetChanged();
39 | }
40 | };
41 | }
42 |
43 | @Override
44 | public Fragment getItem(int position) {
45 | Object listItemObject = mAdAdapter.getItem(position);
46 | if (listItemObject instanceof FlurryAdNative) {
47 | return PostDetailFragment.newInstance((FlurryAdNative)listItemObject);
48 | } else {
49 | return PostDetailFragment.newInstance((Post)listItemObject);
50 | }
51 | }
52 |
53 | @Override
54 | public int getCount() {
55 | return mAdAdapter.getCount();
56 | }
57 |
58 | public void setBlogPosts(List blogPosts) {
59 | mAdAdapter.setData(blogPosts);
60 | notifyDataSetChanged();
61 | }
62 |
63 | public void destroy() {
64 | mAdAdapter.destroy();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/adapters/PostListAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets.adapters;
18 |
19 | import android.content.Context;
20 | import android.util.Log;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.widget.BaseAdapter;
25 | import android.widget.ImageView;
26 | import android.widget.TextView;
27 |
28 | import com.flurry.android.ads.FlurryAdNative;
29 | import com.flurry.android.ads.FlurryAdNativeAsset;
30 | import com.yahoo.mobile.client.android.yodel.R;
31 | import com.yahoo.mobile.client.android.yodel.feed.TumblrFeedManager;
32 | import com.yahoo.mobile.client.android.yodel.utils.AnalyticsHelper;
33 | import com.yahoo.mobile.client.android.yodel.utils.DateTimeUtil;
34 | import com.yahoo.mobile.client.android.yodel.utils.ImageLoader;
35 | import com.tumblr.jumblr.types.LinkPost;
36 | import com.tumblr.jumblr.types.PhotoPost;
37 | import com.tumblr.jumblr.types.Post;
38 | import com.tumblr.jumblr.types.QuotePost;
39 | import com.tumblr.jumblr.types.TextPost;
40 | import com.tumblr.jumblr.types.VideoPost;
41 |
42 | import java.util.List;
43 |
44 | public class PostListAdapter extends BaseAdapter {
45 |
46 | private static final String LOG_TAG = PostListAdapter.class.getName();
47 |
48 | private BaseAdAdapter mAdAdapter;
49 | private Context mContext;
50 | private LayoutInflater mInflater;
51 | private ImageLoader mImageLoader;
52 |
53 | private static final int VIEW_TYPE_COUNT = 2;
54 | private static final int VIEW_TYPE_NORMAL = 0;
55 | private static final int VIEW_TYPE_AD = 1;
56 |
57 | public static final String AD_ASSET_HEADLINE = "headline";
58 | public static final String AD_ASSET_SUMMARY = "summary";
59 | public static final String AD_ASSET_SOURCE = "source";
60 | public static final String AD_ASSET_SEC_HQ_BRANDING_LOGO = "secHqBrandingLogo";
61 | public static final String AD_ASSET_SEC_HQ_IMAGE = "secHqImage";
62 | public static final String AD_ASSET_VIDEO = "videoUrl";
63 |
64 | public PostListAdapter(Context context) {
65 | mInflater = LayoutInflater.from(context);
66 | mImageLoader = ImageLoader.getInstance();
67 | this.mContext = context;
68 | this.mAdAdapter = new BaseAdAdapter(mContext) {
69 | @Override
70 | public void onAdCountChanged() {
71 | notifyDataSetChanged();
72 | }
73 | };
74 | }
75 |
76 | @Override
77 | public View getView(final int position, View convertView, ViewGroup parent) {
78 |
79 | switch (getItemViewType(position)) {
80 | case VIEW_TYPE_AD:
81 | AdViewHolder adHolder;
82 | FlurryAdNative adNative = (FlurryAdNative)getItem(position);
83 |
84 | if (convertView == null) {
85 | adHolder = new AdViewHolder();
86 |
87 | convertView = mInflater.inflate(R.layout.list_item_ad_card, parent, false);
88 |
89 | adHolder.adImage = (ImageView) convertView.findViewById(R.id.ad_image);
90 | adHolder.adVideo = (ViewGroup) convertView.findViewById(R.id.ad_video);
91 | adHolder.adTitle = (TextView) convertView.findViewById(R.id.ad_title);
92 | adHolder.adSummary = (TextView) convertView.findViewById(R.id.ad_summary);
93 | adHolder.publisher = (TextView) convertView.findViewById(R.id.ad_publisher);
94 | adHolder.sponsoredImage = (ImageView) convertView.findViewById(R.id.sponsored_image);
95 |
96 | convertView.setTag(adHolder);
97 | } else {
98 | adHolder = (AdViewHolder) convertView.getTag();
99 | adHolder.adNative.removeTrackingView(); // Remove the old tracking view
100 | }
101 |
102 | adHolder.adNative = adNative;
103 | // Set this convertView as the tracking view so it could open the ad when tapped.
104 | adNative.setTrackingView(convertView);
105 |
106 | loadAdInView(adHolder, adNative);
107 | break;
108 | case VIEW_TYPE_NORMAL:
109 | PostViewHolder postHolder;
110 |
111 | if (convertView == null) {
112 | postHolder = new PostViewHolder();
113 |
114 | convertView = mInflater.inflate(R.layout.list_item_post_card, parent, false);
115 |
116 | postHolder.postImage = (ImageView) convertView.findViewById(R.id.post_image);
117 | postHolder.postTitle = (TextView) convertView.findViewById(R.id.post_title);
118 | postHolder.postSummary = (TextView) convertView.findViewById(R.id.post_summary);
119 | postHolder.postDate = (TextView) convertView.findViewById(R.id.date_text);
120 | postHolder.publisher = (TextView) convertView.findViewById(R.id.post_publisher);
121 | postHolder.retumbleIcon = (ImageView) convertView.findViewById(R.id.icon_reblog);
122 | postHolder.likeIcon = (ImageView) convertView.findViewById(R.id.icon_like);
123 |
124 | convertView.setTag(postHolder);
125 | } else {
126 | postHolder = (PostViewHolder) convertView.getTag();
127 | }
128 |
129 | loadPostInView(postHolder, (Post) getItem(position));
130 | break;
131 | }
132 |
133 | return convertView;
134 | }
135 |
136 | @Override
137 | public int getCount() {
138 | return mAdAdapter.getCount();
139 | }
140 |
141 | @Override
142 | public long getItemId(int position) {
143 | return position;
144 | }
145 |
146 | @Override
147 | public Object getItem(int position) {
148 | return mAdAdapter.getItem(position);
149 | }
150 |
151 | @Override
152 | public int getViewTypeCount() {
153 | return VIEW_TYPE_COUNT;
154 | }
155 |
156 | @Override
157 | public int getItemViewType(int position) {
158 | if (mAdAdapter.canShowAdAtPosition(position)) {
159 | return VIEW_TYPE_AD;
160 | } else {
161 | return VIEW_TYPE_NORMAL;
162 | }
163 | }
164 |
165 | public void setBlogPosts(List blogPosts) {
166 | mAdAdapter.setData(blogPosts);
167 | notifyDataSetChanged();
168 | }
169 |
170 | public void destroy() {
171 | notifyDataSetInvalidated();
172 | mAdAdapter.destroy();
173 | }
174 |
175 | private void loadPostInView(PostViewHolder viewHolder, Post post) {
176 | viewHolder.postTitle.setText(post.getSourceTitle());
177 |
178 | viewHolder.postImage.setImageBitmap(null);
179 |
180 | String type = post.getType();
181 | String blogName, title, body, photoUrl, postDate;
182 | blogName = post.getBlogName();
183 | title = body = photoUrl = null;
184 | postDate = DateTimeUtil.getFriendlyDateString(post.getTimestamp() * 1000, mContext);
185 |
186 | switch (type) {
187 | case TumblrFeedManager.POST_TYPE_TEXT:
188 | title = ((TextPost)post).getTitle();
189 | body = ((TextPost)post).getBody();
190 | break;
191 | case TumblrFeedManager.POST_TYPE_QUOTE:
192 | body = ((QuotePost)post).getText() + "\n - " + ((QuotePost)post).getSource();
193 | break;
194 | case TumblrFeedManager.POST_TYPE_PHOTO:
195 | body = ((PhotoPost)post).getCaption();
196 | // It can't be a photo post without photos. Either way...
197 | if (((PhotoPost)post).getPhotos().size() > 0) {
198 | photoUrl = ((PhotoPost) post).getPhotos().get(0).getOriginalSize().getUrl();
199 | }
200 | break;
201 | case TumblrFeedManager.POST_TYPE_VIDEO:
202 | body = ((VideoPost)post).getCaption();
203 | photoUrl = ((VideoPost)post).getThumbnailUrl();
204 | break;
205 | case TumblrFeedManager.POST_TYPE_LINK:
206 | title = ((LinkPost)post).getTitle();
207 | body = ((LinkPost)post).getDescription() + "\n" + ((LinkPost)post).getLinkUrl();
208 | break;
209 | }
210 | viewHolder.publisher.setText(blogName);
211 | viewHolder.publisher.setTextColor(mContext.getResources().getColor(R.color.y_blue));
212 |
213 | if (title != null) {
214 | viewHolder.postTitle.setVisibility(View.VISIBLE);
215 | viewHolder.postTitle.setText(title);
216 | } else {
217 | viewHolder.postTitle.setVisibility(View.GONE);
218 | }
219 | if (body != null) {
220 | viewHolder.postSummary.setVisibility(View.VISIBLE);
221 | viewHolder.postSummary.setText(body);
222 | } else {
223 | viewHolder.postSummary.setVisibility(View.VISIBLE);
224 | }
225 | if (photoUrl != null) {
226 | viewHolder.postImage.setVisibility(View.VISIBLE);
227 | mImageLoader.displayImage(photoUrl, viewHolder.postImage);
228 | } else {
229 | viewHolder.postImage.setVisibility(View.GONE);
230 | }
231 |
232 | viewHolder.postDate.setText(postDate);
233 |
234 | }
235 |
236 | public void loadAdInView(AdViewHolder viewHolder, FlurryAdNative adNative) {
237 | try {
238 | mAdAdapter.loadAdAssetInView(adNative, AD_ASSET_HEADLINE, viewHolder.adTitle);
239 | mAdAdapter.loadAdAssetInView(adNative, AD_ASSET_SUMMARY, viewHolder.adSummary);
240 | mAdAdapter.loadAdAssetInView(adNative, AD_ASSET_SOURCE, viewHolder.publisher);
241 |
242 | FlurryAdNativeAsset adNativeAsset = adNative.getAsset(AD_ASSET_SEC_HQ_BRANDING_LOGO);
243 | mImageLoader.displayImage(adNativeAsset.getValue(), viewHolder.sponsoredImage);
244 | if (adNative.isVideoAd()) {
245 | mAdAdapter.loadAdAssetInView(adNative, AD_ASSET_VIDEO, viewHolder.adVideo);
246 | } else {
247 | adNativeAsset = adNative.getAsset(AD_ASSET_SEC_HQ_IMAGE);
248 | mImageLoader.displayImage(adNativeAsset.getValue(), viewHolder.adImage);
249 | }
250 |
251 | } catch (Exception e) {
252 | Log.i(LOG_TAG, "Exception in fetching an Ad");
253 | AnalyticsHelper.logError(LOG_TAG, "Exception in fetching an ad", e);
254 | }
255 | }
256 |
257 | public static class PostViewHolder
258 | {
259 | ImageView postImage;
260 | TextView postTitle;
261 | TextView postSummary;
262 | TextView postDate;
263 | TextView publisher;
264 | ImageView retumbleIcon;
265 | ImageView likeIcon;
266 | }
267 |
268 | public static class AdViewHolder
269 | {
270 | ImageView adImage;
271 | ViewGroup adVideo;
272 | TextView adTitle;
273 | TextView adSummary;
274 | TextView publisher;
275 | ImageView sponsoredImage;
276 | FlurryAdNative adNative;
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/ui/widgets/adapters/PostSearchListAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.ui.widgets.adapters;
18 |
19 | import android.content.Context;
20 | import android.view.LayoutInflater;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.widget.BaseAdapter;
24 | import android.widget.ImageView;
25 | import android.widget.TextView;
26 |
27 | import com.yahoo.mobile.client.android.yodel.R;
28 | import com.yahoo.mobile.client.android.yodel.utils.ImageLoader;
29 | import com.tumblr.jumblr.types.PhotoPost;
30 | import com.tumblr.jumblr.types.Post;
31 |
32 | import java.util.List;
33 |
34 | public class PostSearchListAdapter extends BaseAdapter {
35 |
36 | private List mPosts;
37 | private Context mContext;
38 |
39 | public PostSearchListAdapter(Context context) {
40 | super();
41 | this.mContext = context;
42 | }
43 | @Override
44 | public View getView(int position, View convertView, ViewGroup parent) {
45 | ViewHolder holder;
46 |
47 | if (convertView == null) {
48 | holder = new ViewHolder();
49 |
50 | convertView = LayoutInflater
51 | .from(mContext).inflate(R.layout.list_item_post_search, parent, false);
52 |
53 | holder.postImage = (ImageView) convertView.findViewById(R.id.post_image);
54 | holder.publisher = (TextView) convertView.findViewById(R.id.post_publisher);
55 | holder.postSummary = (TextView) convertView.findViewById(R.id.post_summary);
56 |
57 | convertView.setTag(holder);
58 | } else {
59 | holder = (ViewHolder) convertView.getTag();
60 | }
61 |
62 | Post post = mPosts.get(position);
63 | String type = post.getType();
64 | String blogName, caption, photoUrl;
65 | blogName = post.getBlogName();
66 |
67 | // We are only concerned with photo posts for this scenario
68 | if ("photo".equals(type)) {
69 | caption = ((PhotoPost)post).getCaption();
70 | // It can't be a photo post without photos. Either way...
71 | photoUrl = ((PhotoPost) post).getPhotos().get(0).getOriginalSize().getUrl();
72 |
73 | holder.postSummary.setText(caption);
74 | ImageLoader.getInstance().displayImage(photoUrl, holder.postImage);
75 | }
76 |
77 | holder.publisher.setText(blogName);
78 |
79 | return convertView;
80 | }
81 |
82 | @Override
83 | public int getCount() {
84 | return mPosts != null ? mPosts.size() : 0;
85 | }
86 |
87 | @Override
88 | public Object getItem(int position) {
89 | return mPosts.get(position);
90 | }
91 |
92 | @Override
93 | public long getItemId(int position) {
94 | return position;
95 | }
96 |
97 | public void setBlogPosts(List blogPosts) {
98 | mPosts = blogPosts;
99 | notifyDataSetChanged();
100 | }
101 |
102 | public static class ViewHolder
103 | {
104 | ImageView postImage;
105 | TextView postSummary;
106 | TextView publisher;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/utils/AnalyticsHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.utils;
18 |
19 | import com.flurry.android.FlurryAgent;
20 |
21 | import java.util.Map;
22 |
23 | /**
24 | * Helps with logging custom events and errors to Flurry
25 | */
26 | public class AnalyticsHelper {
27 | public static final String EVENT_STREAM_ARTICLE_CLICK = "stream_article_click";
28 | public static final String EVENT_STREAM_AD_CLICK = "stream_ad_click";
29 | public static final String EVENT_STREAM_SEARCH_CLICK = "stream_search_click";
30 | public static final String EVENT_STREAM_PULL_REFRESH = "stream_pullto_refresh";
31 | public static final String EVENT_CAR_MOREIMG_CLICK = "carousel_moreimages_click";
32 | public static final String EVENT_CAR_LEARNMORE_CLICK = "carousel_learnmore_click";
33 | public static final String EVENT_CAR_CONTENT_SWIPE = "carousel_content_swipe";
34 | public static final String EVENT_AD_CLOSEBUTTON_CLICK = "ad_closebutton_click";
35 | public static final String EVENT_SEARCH_STARTED = "search_term_started";
36 | public static final String EVENT_SEARCH_MOREONWEB_CLICK = "search_moreonweb_click";
37 | public static final String EVENT_SEARCH_RESULT_CLICK = "search_result_click";
38 |
39 | public static final String PARAM_ARTICLE_ORIGIN = "article_origin";
40 | public static final String PARAM_ARTICLE_TYPE = "article_type";
41 | public static final String PARAM_CONTENT_LOADED = "content_loaded";
42 | public static final String PARAM_SEARCH_TERM = "search_term";
43 |
44 | /**
45 | * Logs an event for analytics.
46 | *
47 | * @param eventName name of the event
48 | * @param eventParams event parameters (can be null)
49 | * @param timed true
if the event should be timed, false otherwise
50 | */
51 | public static void logEvent(String eventName, Map eventParams, boolean timed) {
52 | FlurryAgent.logEvent(eventName, eventParams, timed);
53 | }
54 |
55 | /**
56 | * Ends a timed event that was previously started.
57 | *
58 | * @param eventName name of the event
59 | * @param eventParams event parameters (can be null)
60 | */
61 | public static void endTimedEvent(String eventName, Map eventParams) {
62 | FlurryAgent.endTimedEvent(eventName, eventParams);
63 | }
64 |
65 | /**
66 | * Logs an error.
67 | *
68 | * @param errorId error ID
69 | * @param errorDescription error description
70 | * @param throwable a {@link Throwable} that describes the error
71 | */
72 | public static void logError(String errorId, String errorDescription, Throwable throwable) {
73 | FlurryAgent.onError(errorId, errorDescription, throwable);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/utils/DateTimeUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.utils;
18 |
19 | import android.content.Context;
20 | import android.text.format.DateFormat;
21 |
22 | import com.yahoo.mobile.client.android.yodel.R;
23 |
24 | import java.util.Date;
25 |
26 | public final class DateTimeUtil {
27 |
28 | /**
29 | * Gets the current time in epoch time milliseconds.
30 | * @return the current time in epoch time milliseconds.
31 | */
32 | public static long getNow() {
33 | return new Date().getTime();
34 | }
35 |
36 | /**
37 | * Gets the local time as a Date object.
38 | *
39 | * @param unixTimeStampMillis the epoch time in milliseconds.
40 | * @return the {@link java.util.Date} representing the local time.
41 | */
42 | public static Date getLocalDateFromTicks(long unixTimeStampMillis) {
43 | return new Date(unixTimeStampMillis);
44 | }
45 |
46 | /**
47 | * Gets a simple representation of the time at time in which something occurred relative
48 | * to now.
49 | *
50 | * E.g. "1 day ago", "3 hours ago", "Just now"
51 | * @param unixTimeStampMillis the epoch time in the past in milliseconds.
52 | * @param context the current application context.
53 | * @return a string representation of the time.
54 | */
55 | public static String getFriendlyDateString(long unixTimeStampMillis, Context context) {
56 | // Don't trust this method in prod. Currently, 1.4 days ago is "yesterday"
57 | long timeDiffMs = getNow() - unixTimeStampMillis;
58 | long timeDiffMinutes = timeDiffMs / (1000 * 60);
59 | long timeDiffHours = timeDiffMinutes / 60;
60 | long timeDiffDays = timeDiffHours / 24;
61 |
62 | if (timeDiffDays > 7) {
63 | return DateFormat.getMediumDateFormat(context)
64 | .format(getLocalDateFromTicks(unixTimeStampMillis));
65 | } else if (timeDiffDays > 0) {
66 | return context.getResources()
67 | .getQuantityString(R.plurals.friendly_date_day_ago,
68 | (int)timeDiffDays, (int)timeDiffDays);
69 | } else if (timeDiffHours > 0) {
70 | return context.getResources()
71 | .getQuantityString(R.plurals.friendly_date_hour_ago,
72 | (int)timeDiffHours, (int)timeDiffHours);
73 | } else if (timeDiffMinutes > 0) {
74 | return context.getResources()
75 | .getQuantityString(R.plurals.friendly_date_minute_ago,
76 | (int)timeDiffMinutes, (int)timeDiffMinutes);
77 | } else {
78 | return context.getResources().getString(R.string.friendly_date_now);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/utils/ImageLoader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.utils;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.BitmapFactory;
21 | import android.os.Build;
22 | import android.text.TextUtils;
23 | import android.util.DisplayMetrics;
24 | import android.widget.ImageView;
25 |
26 | import com.yahoo.mobile.client.android.yodel.NativeTestAppApplication;
27 |
28 | import java.io.InputStream;
29 | import java.lang.ref.WeakReference;
30 | import java.net.HttpURLConnection;
31 | import java.net.URL;
32 | import java.net.URLConnection;
33 | import java.util.concurrent.ExecutorService;
34 | import java.util.concurrent.Executors;
35 |
36 | public class ImageLoader {
37 | private final MemoryCache memoryCache = new MemoryCache();
38 | private final ExecutorService executorService;
39 |
40 | private static ImageLoader sInstance = null;
41 |
42 | private ImageLoader() {
43 | executorService = Executors.newFixedThreadPool(2);
44 | }
45 |
46 | public static ImageLoader getInstance() {
47 | synchronized (ImageLoader.class) {
48 | if (sInstance == null) {
49 | sInstance = new ImageLoader();
50 | }
51 | }
52 |
53 | return sInstance;
54 | }
55 |
56 | public void displayImage(String url, ImageView imageView) {
57 | if (TextUtils.isEmpty(url) || imageView == null) {
58 | return;
59 | }
60 |
61 | imageView.setTag(url);
62 | Bitmap bitmap = memoryCache.get(url);
63 | if (bitmap != null) {
64 | setImageViewBitmap(imageView, bitmap);
65 | } else {
66 | executorService.submit(new PhotosLoader(url, imageView));
67 | }
68 | }
69 |
70 | private Bitmap getBitmap(String url) {
71 | if (url == null) {
72 | return null;
73 | }
74 |
75 | URLConnection conn = null;
76 | InputStream is = null;
77 |
78 | try {
79 | final URL imageUrl = new URL(url);
80 | conn = imageUrl.openConnection();
81 | conn.setConnectTimeout(10000);
82 | conn.setReadTimeout(30000);
83 | is = conn.getInputStream();
84 | return BitmapFactory.decodeStream(is, null, null);
85 | } catch (Exception ex) {
86 | return null;
87 | } finally {
88 | if (is != null) {
89 | try {
90 | is.close();
91 | } catch (Exception e) {
92 | // Deliberately empty
93 | }
94 | }
95 | if (conn != null) {
96 | if (conn instanceof HttpURLConnection) {
97 | ((HttpURLConnection) conn).disconnect();
98 | }
99 | }
100 | }
101 | }
102 |
103 | private class PhotosLoader implements Runnable {
104 | private String mUrl;
105 | private WeakReference mImageView;
106 |
107 | public PhotosLoader(final String url, final ImageView imageView) {
108 | mUrl = url;
109 | mImageView = new WeakReference<>(imageView);
110 | }
111 |
112 | @Override
113 | public void run() {
114 | ImageView imageView = mImageView.get();
115 | if (imageView == null || !mUrl.equals(imageView.getTag())) {
116 | return;
117 | }
118 |
119 | final Bitmap bmp = getBitmap(mUrl);
120 | if (bmp != null) {
121 | memoryCache.put(mUrl, bmp);
122 |
123 | final ImageView imageViewToLoad = mImageView.get();
124 | if (!mUrl.equals(imageView.getTag())) {
125 | return;
126 | }
127 |
128 | NativeTestAppApplication.getInstance().getMainThreadHandler().post(new Runnable() {
129 | @Override
130 | public void run() {
131 | setImageViewBitmap(imageViewToLoad, bmp);
132 | }
133 | });
134 | }
135 | }
136 | }
137 |
138 | private void setImageViewBitmap(ImageView image, Bitmap bitmap) {
139 | String deviceName = getDeviceName();
140 | if (!TextUtils.isEmpty(deviceName) && deviceName.toUpperCase().contains("SAMSUNG")) {
141 | bitmap.setDensity(DisplayMetrics.DENSITY_HIGH);
142 | }
143 | image.setImageBitmap(bitmap);
144 | }
145 |
146 | private String getDeviceName() {
147 | String manufacturer = Build.MANUFACTURER;
148 | String model = Build.MODEL;
149 | if (model.startsWith(manufacturer)) {
150 | return model;
151 | } else {
152 | return manufacturer + " " + model;
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/utils/MemoryCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.utils;
18 |
19 | import android.graphics.Bitmap;
20 | import android.support.v4.util.LruCache;
21 | import android.text.TextUtils;
22 | import android.util.Log;
23 |
24 | public class MemoryCache {
25 | private static String LOG_TAG = MemoryCache.class.getSimpleName();
26 |
27 | private final LruCache mCache;
28 |
29 | public MemoryCache() {
30 | int cacheSize = 10 * 1024 * 1024 / 1024; // 10 mB
31 | Log.i(LOG_TAG, "Image cache size: " + cacheSize + "kB");
32 |
33 | mCache = new LruCache(cacheSize) {
34 | @Override
35 | protected int sizeOf(String key, Bitmap value) {
36 | return value.getByteCount() / 1024;
37 | }
38 | };
39 | }
40 |
41 | public Bitmap get(String key) {
42 | if (TextUtils.isEmpty(key)) {
43 | return null;
44 | }
45 |
46 | return mCache.get(key);
47 | }
48 |
49 | public Bitmap put(String key, Bitmap value) {
50 | if (TextUtils.isEmpty(key) || value == null) {
51 | return null;
52 | }
53 |
54 | return mCache.put(key, value);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/yodel-sample/src/com/yahoo/mobile/client/android/yodel/utils/PostDataLoader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Yahoo Inc. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yahoo.mobile.client.android.yodel.utils;
18 |
19 |
20 | import android.content.Context;
21 | import android.support.v4.content.AsyncTaskLoader;
22 |
23 | import com.yahoo.mobile.client.android.yodel.feed.TumblrFeedManager;
24 | import com.tumblr.jumblr.types.Post;
25 |
26 | import java.util.List;
27 |
28 | public class PostDataLoader extends AsyncTaskLoader> {
29 |
30 | private List mPostsCache;
31 | private final String mTagQuery;
32 |
33 | public PostDataLoader(Context context) {
34 | this(context, null);
35 | }
36 |
37 | public PostDataLoader(Context context, String tagQuery) {
38 | super(context);
39 | this.mTagQuery = tagQuery;
40 | }
41 |
42 | @Override
43 | public List loadInBackground() {
44 | if (mTagQuery == null) {
45 | return TumblrFeedManager.getRecentPosts();
46 | } else {
47 | return TumblrFeedManager.getPostsWithTag(mTagQuery);
48 | }
49 | }
50 |
51 | /**
52 | * Derived classes must call through to the super class's implementation of this method, kthnx
53 | */
54 | @Override
55 | protected void onStartLoading() {
56 | if (mPostsCache != null) {
57 | deliverResult(mPostsCache);
58 | } else {
59 | forceLoad();
60 | }
61 | }
62 |
63 | @Override
64 | public void deliverResult(List data) {
65 | if (isReset()) {
66 | mPostsCache = null;
67 | return;
68 | }
69 |
70 | mPostsCache = data;
71 | super.deliverResult(data);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------