提交 7a8ac09c 编写于 作者: S Sam Judd

Add an Imgur card sample app.

上级 38d0d782
......@@ -19,6 +19,7 @@ ANDROID_SUPPORT_VERSION=26.0.2
VOLLEY_VERSION=1.0.0
OK_HTTP_VERSION=3.9.0
ANDROID_GRADLE_VERSION=2.3.0
DAGGER_VERSION=2.10
COVERALLS_GRADLE_VERSION=2.4.0
JUNIT_VERSION=4.12
......
......@@ -69,6 +69,7 @@ afterEvaluate {
if (tasks.findByName("assembleDebug") == null) {
return
}
task findbugs(type: FindBugs, dependsOn: assembleDebug) {
description 'Run findbugs'
......
apply plugin: 'com.android.application'
android {
compileSdkVersion COMPILE_SDK_VERSION as int
buildToolsVersion BUILD_TOOLS_VERSION
defaultConfig {
applicationId "com.bumptech.glide.samples.imgur"
minSdkVersion MIN_SDK_VERSION as int
targetSdkVersion TARGET_SDK_VERSION as int
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile project(':library')
annotationProcessor project(':annotation:compiler')
compile "com.google.dagger:dagger:${DAGGER_VERSION}"
annotationProcessor("com.google.dagger:dagger-compiler:${DAGGER_VERSION}", {
exclude group: 'com.google.code.findbugs', module: "jsr305"
})
compile("com.google.dagger:dagger-android:${DAGGER_VERSION}", {
exclude group: 'com.google.code.findbugs', module: "jsr305"
})
compile "com.squareup.okhttp3:okhttp:${OK_HTTP_VERSION}"
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.2.0'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'io.reactivex:rxjava:1.2.9'
compile "com.android.support:appcompat-v7:${ANDROID_SUPPORT_VERSION}"
compile "com.android.support:cardview-v7:${ANDROID_SUPPORT_VERSION}"
compile "com.android.support:recyclerview-v7:${ANDROID_SUPPORT_VERSION}"
}
task run(type: Exec, dependsOn: 'installDebug') {
description 'Installs the APK and runs the main activity: "gradlew :samples:???:run"'
commandLine "${android.sdkDirectory}/platform-tools/adb", 'shell', 'am', 'start', '-n', 'com.bumptech.glide.samples.card/.MainActivity'
}
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Not supported by all build systems -->
<issue id="GradleOverrides" severity="ignore" />
<issue id="IconMissingDensityFolder" severity="ignore"/>
<issue id="GoogleAppIndexingWarning" severity="ignore"/>
</lint>
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/judds/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bumptech.glide.samples.imgur">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:name="com.bumptech.glide.samples.imgur.ImgurApplication">
<activity android:name="com.bumptech.glide.samples.imgur.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
package com.bumptech.glide.samples.imgur;
import dagger.Module;
import dagger.Provides;
import okhttp3.OkHttpClient;
/**
* The Application Dagger module for the Imgur sample.
*/
@Module
public class ApplicationModule {
@Provides
OkHttpClient okHttpClient() {
return new OkHttpClient();
}
}
package com.bumptech.glide.samples.imgur;
import android.app.Activity;
import android.app.Application;
import dagger.android.DispatchingAndroidInjector;
import dagger.android.HasDispatchingActivityInjector;
import javax.inject.Inject;
/**
* Runs Dagger injection in the Imgur sample.
*/
public final class ImgurApplication extends Application
implements HasDispatchingActivityInjector {
@Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerImgurApplicationComponent.create()
.inject(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
}
package com.bumptech.glide.samples.imgur;
import com.bumptech.glide.samples.imgur.api.ApiModule;
import dagger.Component;
import javax.inject.Singleton;
/**
* Specifies Dagger modules for {@link ImgurApplication}.
*/
@Singleton
@Component(modules = {MainActivityModule.class, ApplicationModule.class, ApiModule.class})
public interface ImgurApplicationComponent {
void inject(ImgurApplication application);
}
package com.bumptech.glide.samples.imgur;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
/**
* Generates a Glide API for the Imgur sample.
*/
@GlideModule(glideName = "ImgurGlide")
public class ImgurGlideModule extends AppGlideModule {
// Intentionally Empty.
}
package com.bumptech.glide.samples.imgur;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.samples.imgur.api.Image;
import dagger.android.AndroidInjection;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import rx.Observable;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* Displays images and GIFs from Imgur in a scrollable list of cards.
*/
public final class MainActivity extends AppCompatActivity {
@Inject @Named("hotViralImages") Observable<List<Image>> fetchImagesObservable;
private ImgurImageAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new ImgurImageAdapter();
recyclerView.setAdapter(adapter);
fetchImagesObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Image>>() {
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
@Override
public void onNext(List<Image> images) {
adapter.setData(images);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
fetchImagesObservable.unsubscribeOn(AndroidSchedulers.mainThread());
}
private final class ImgurImageAdapter extends RecyclerView.Adapter<ViewHolder> {
private List<Image> images = Collections.emptyList();
public void setData(@NonNull List<Image> images) {
this.images = images;
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.image_card, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder vh = (ViewHolder) holder;
Image image = images.get(position);
vh.title.setText(
TextUtils.isEmpty(image.title) ? image.description : image.title);
ImgurGlide.with(vh.imageView)
.load(image.link)
.into(vh.imageView);
}
@Override
public int getItemCount() {
return images.size();
}
private final class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final TextView title;
ViewHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.image);
title = (TextView) itemView.findViewById(R.id.title);
}
}
}
}
package com.bumptech.glide.samples.imgur;
import android.app.Activity;
import dagger.Binds;
import dagger.Module;
import dagger.android.ActivityKey;
import dagger.android.AndroidInjector;
import dagger.multibindings.IntoMap;
@Module(subcomponents = MainActivitySubcomponent.class)
abstract class MainActivityModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindYourActivityInjectorFactory(
MainActivitySubcomponent.Builder builder);
}
package com.bumptech.glide.samples.imgur;
import dagger.Subcomponent;
import dagger.android.AndroidInjector;
/**
* The subcomponent for the Imgur sample's main activity.
*/
@Subcomponent
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
/**
* Dagger componeent for Activity injection.
*/
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
// Intentionally empty.
}
}
package com.bumptech.glide.samples.imgur.api;
import dagger.Module;
import dagger.Provides;
import java.io.IOException;
import java.util.List;
import javax.inject.Named;
import javax.inject.Singleton;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Observable;
/**
* Provides classes related to the Imgur API via Dagger.
*/
@Singleton
@Module
public final class ApiModule {
@Singleton
@Named("hotViralImages")
@Provides
Observable<List<Image>> provideHotViralImages(ImgurObservables imgurObservables) {
return imgurObservables.getHotViralImages(5 /*maxPages*/);
}
@Provides ImgurObservables imgurObservables(ImgurService imgurService) {
return new ImgurObservables(imgurService);
}
@Provides ImgurService getImgurService(Retrofit retrofit) {
return retrofit.create(ImgurService.class);
}
@Provides Retrofit retrofit() {
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(
chain.request()
.newBuilder()
.addHeader("Authorization", "Client-ID " + ImgurService.CLIENT_ID)
.build());
}
})
.build();
return new Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl("https://api.imgur.com/3/")
.build();
}
}
package com.bumptech.glide.samples.imgur.api;
import java.util.List;
/**
* Represents Imgur's Gallery resource.
*
* <p>Populated automatically by GSON.
*/
public final class Gallery {
public List<Image> data;
@Override
public String toString() {
return "Gallery{"
+ "data=" + data
+ '}';
}
}
package com.bumptech.glide.samples.imgur.api;
/**
* Represents Imgur's Image resource.
*
* <p>Populated automatically by GSON
*/
public final class Image {
public String id;
public String title;
public String description;
public String link;
public boolean is_album;
@Override
public String toString() {
return "Image{"
+ "id='" + id + '\''
+ ", title='" + title + '\''
+ ", description='" + description + '\''
+ ", link='" + link + '\''
+ ", is_album='" + is_album + '\''
+ '}';
}
}
package com.bumptech.glide.samples.imgur.api;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import rx.Observable;
import rx.functions.Func1;
import rx.functions.Func2;
/**
* Observables for retrieving metadata from Imgur's API.
*/
public final class ImgurObservables {
private final ImgurService imgurService;
ImgurObservables(ImgurService imgurService) {
this.imgurService = imgurService;
}
public Observable<List<Image>> getHotViralImages(int maxPages) {
return Observable.range(0, maxPages)
.flatMap(new Func1<Integer, Observable<List<Image>>>() {
@Override
public Observable<List<Image>> call(Integer integer) {
return imgurService.getHotViral(integer).map(new GetData()).flatMap(
new Func1<List<Image>, Observable<List<Image>>>() {
@Override
public Observable<List<Image>> call(List<Image> images) {
for (Iterator<Image> iterator = images.iterator(); iterator.hasNext();) {
if (iterator.next().is_album) {
iterator.remove();
}
}
return Observable.just(images);
}
});
}
})
.takeWhile(new Func1<List<Image>, Boolean>() {
@Override
public Boolean call(List<Image> images) {
return !images.isEmpty();
}
})
.scan(new Func2<List<Image>, List<Image>, List<Image>>() {
@Override
public List<Image> call(List<Image> images, List<Image> images2) {
List<Image> result = new ArrayList<>(images.size() + images2.size());
result.addAll(images);
result.addAll(images2);
return result;
}
})
.cache();
}
private static class GetData implements Func1<Gallery, List<Image>> {
@Override
public List<Image> call(Gallery gallery) {
return gallery.data;
}
}
}
package com.bumptech.glide.samples.imgur.api;
import retrofit2.http.GET;
import retrofit2.http.Path;
import rx.Observable;
/**
* Define's Imgur's API for Retrofit.
*/
public interface ImgurService {
String CLIENT_ID = "36d1f6bef16370c";
@GET("gallery/hot/viral/{page}")
Observable<Gallery> getHotViral(@Path("page") int page);
@GET("gallery/hot/{sort}/{page}.json")
Observable<Gallery> getHot(@Path("sort") Sort sort, @Path("page") int page);
@GET("gallery/{section}/{sort}/{page}.json")
Observable<Gallery> getGallery(@Path("section") Section section,
@Path("sort") Sort sort, @Path("page") int page);
/**
* Sections that Imgur's API allows us to query from.
*/
enum Section {
hot,
top,
user
}
/**
* The sort order for content within a particular section.
*/
enum Sort {
viral,
top,
time,
rising
}
}
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.bumptech.glide.samples.imgur.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
/>
</merge>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<android.support.v7.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:padding="16dp"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:scaleType="fitCenter"
android:layout_width="match_parent"
android:layout_height="150dp"
android:contentDescription="@null"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="16dp"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
<resources>
<string name="app_name">Imgur</string>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
......@@ -12,6 +12,7 @@ include ':samples:giphy'
include ':samples:svg'
include ':samples:gallery'
include ':samples:contacturi'
include ':samples:imgur'
include ':integration'
include ':integration:volley'
include ':integration:okhttp'
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册