From c5e9aa629982a455aed68a7ee1b0c04a13f9fac9 Mon Sep 17 00:00:00 2001 From: Tayrinn Date: Thu, 4 Aug 2016 17:18:49 +0300 Subject: [PATCH 1/3] first working demo --- app/build.gradle | 10 + app/src/main/java/ru/yandex/yamblz/App.java | 10 + .../java/ru/yandex/yamblz/data/Artist.java | 192 ++++++++++++++++++ .../java/ru/yandex/yamblz/data/Cover.java | 57 ++++++ .../yandex/yamblz/retrofit/ApiServices.java | 57 ++++++ .../ui/adapters/FrameItemDecoration.java | 23 +++ .../ui/adapters/GenresRecyclerAdapter.java | 133 ++++++++++++ .../yamblz/ui/fragments/ContentFragment.java | 76 ++++++- app/src/main/res/layout/fragment_content.xml | 9 +- app/src/main/res/layout/list_genre_item.xml | 29 +++ app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/dimens.xml | 2 + dependencies.gradle | 12 +- gradle.properties | 2 +- 14 files changed, 605 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/ru/yandex/yamblz/data/Artist.java create mode 100644 app/src/main/java/ru/yandex/yamblz/data/Cover.java create mode 100644 app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java create mode 100644 app/src/main/java/ru/yandex/yamblz/ui/adapters/FrameItemDecoration.java create mode 100644 app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java create mode 100644 app/src/main/res/layout/list_genre_item.xml diff --git a/app/build.gradle b/app/build.gradle index 445372a..f01a51e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -105,6 +105,16 @@ dependencies { compile libraries.lynx compile libraries.processPhoenix + compile libraries.rxjava + compile libraries.rxandroid + compile libraries.jacksonConverter + compile libraries.jacksonDatabind + compile libraries.jacksonAnnotations + compile libraries.retrofit + compile libraries.retrofitAdapter + compile libraries.retrofitAdapter + compile libraries.universalImageLoader + testCompile libraries.junit testCompile libraries.robolectric testCompile libraries.assertJ diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index e5f9972..7b51ef2 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -4,6 +4,9 @@ import android.content.Context; import android.support.annotation.NonNull; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; + import ru.yandex.yamblz.developer_settings.DevMetricsProxy; import ru.yandex.yamblz.developer_settings.DeveloperSettingsModel; import ru.yandex.yamblz.handler.CriticalSectionsManager; @@ -36,6 +39,13 @@ public void onCreate() { CollageLoaderManager.init(null); // add implementation CriticalSectionsManager.init(null); // add implementation + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this) + .diskCacheSize( 20 * 1024 * 1024 ) // 20Mb + .memoryCacheSize( 2 * 1024 * 1024 ) // 2Mb + .threadPoolSize( 5 ) + .diskCacheFileCount( 100 ) + .build(); + ImageLoader.getInstance().init( config ); } @NonNull diff --git a/app/src/main/java/ru/yandex/yamblz/data/Artist.java b/app/src/main/java/ru/yandex/yamblz/data/Artist.java new file mode 100644 index 0000000..c14ca79 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/Artist.java @@ -0,0 +1,192 @@ +package ru.yandex.yamblz.data; + +/** + * Created by Volha on 01.08.2016. + */ + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import java.util.ArrayList; +import java.util.List; + + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonPropertyOrder( { + "id", + "name", + "genres", + "tracks", + "albums", + "link", + "description", + "cover" +}) +public class Artist { + + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("genres") + private List genres = new ArrayList<>(); + @JsonProperty("tracks") + private Integer tracks; + @JsonProperty("albums") + private Integer albums; + @JsonProperty("link") + private String link; + @JsonProperty("description") + private String description; + @JsonProperty("cover") + private Cover cover; + + /** + * @return The id + */ + @JsonProperty("id") + public Integer getId() { + return id; + } + + /** + * @param id The id + */ + @JsonProperty("id") + public void setId(Integer id) { + this.id = id; + } + + /** + * @return The name + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * @param name The name + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + /** + * @return The genres + */ + @JsonProperty("genres") + public List getGenres() { + return genres; + } + + /** + * @param genres The genres + */ + @JsonProperty("genres") + public void setGenres(List genres) { + this.genres = genres; + } + + /* + generate a semicolon separated string + * */ + @JsonIgnore + public String getGenresString() { + StringBuilder sb = new StringBuilder(); + if (genres.size() == 0) + return ""; + sb.append(genres.get(0)); + for (int i = 1; i < genres.size(); ++i) { + sb.append(", "); + sb.append(genres.get(i)); + } + return sb.toString(); + } + + /** + * @return The tracks + */ + @JsonProperty("tracks") + public Integer getTracks() { + return tracks; + } + + /** + * @param tracks The tracks + */ + @JsonProperty("tracks") + public void setTracks(Integer tracks) { + this.tracks = tracks; + } + + /** + * @return The albums + */ + @JsonProperty("albums") + public Integer getAlbums() { + return albums; + } + + /** + * @param albums The albums + */ + @JsonProperty("albums") + public void setAlbums(Integer albums) { + this.albums = albums; + } + + /** + * @return The link + */ + @JsonProperty("link") + public String getLink() { + return link; + } + + /** + * @param link The link + */ + @JsonProperty("link") + public void setLink(String link) { + this.link = link; + } + + /** + * @return The description + */ + @JsonProperty("description") + public String getDescription() { + return description; + } + + /** + * @param description The description + */ + @JsonProperty("description") + public void setDescription(String description) { + this.description = description; + } + + /** + * @return The cover + */ + @JsonProperty("cover") + public Cover getCover() { + return cover; + } + + /** + * @param cover The cover + */ + @JsonProperty("cover") + public void setCover(Cover cover) { + this.cover = cover; + } +} + diff --git a/app/src/main/java/ru/yandex/yamblz/data/Cover.java b/app/src/main/java/ru/yandex/yamblz/data/Cover.java new file mode 100644 index 0000000..cd73070 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/Cover.java @@ -0,0 +1,57 @@ +package ru.yandex.yamblz.data; + +/** + * Created by Volha on 01.08.2016. + */ + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder( { + "small", + "big" +}) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Cover { + + @JsonProperty("small") + private String small; + @JsonProperty("big") + private String big; + + /** + * @return The small + */ + @JsonProperty("small") + public String getSmall() { + return small; + } + + /** + * @param small The small + */ + @JsonProperty("small") + public void setSmall( String small ) { + this.small = small; + } + + /** + * @return The big + */ + @JsonProperty("big") + public String getBig() { + return big; + } + + /** + * @param big The big + */ + @JsonProperty("big") + public void setBig( String big ) { + this.big = big; + } +} + diff --git a/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java b/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java new file mode 100644 index 0000000..142eea5 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java @@ -0,0 +1,57 @@ +package ru.yandex.yamblz.retrofit; + +import java.util.List; + +import ru.yandex.yamblz.data.Artist; +import java.util.List; + +import retrofit.JacksonConverterFactory; +import retrofit.Retrofit; +import retrofit.RxJavaCallAdapterFactory; +import retrofit.http.GET; +import rx.Observable; +import rx.plugins.RxJavaErrorHandler; +import rx.plugins.RxJavaPlugins; +/** + * Created by Volha on 01.08.2016. + */ + +public class ApiServices { + + public final static String API_URL = "http://cache-default04e.cdn.yandex.net/download.cdn.yandex.net/"; + + private final ApiWebService webService; + + static { + RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() { + @Override + public void handleError(Throwable e) { + e.printStackTrace(); + } + }); + } + + public ApiServices() { + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl( API_URL ) + .addConverterFactory( JacksonConverterFactory.create() ) + .addCallAdapterFactory( RxJavaCallAdapterFactory.create() ) + .build(); + + webService = retrofit.create( ApiWebService.class ); + } + + public interface ApiWebService { + + @GET("mobilization-2016/artists.json") + Observable> getArtists(); + } + + public rx.Observable> getArtists() { + + return webService.getArtists(); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/yandex/yamblz/ui/adapters/FrameItemDecoration.java b/app/src/main/java/ru/yandex/yamblz/ui/adapters/FrameItemDecoration.java new file mode 100644 index 0000000..07fe313 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/adapters/FrameItemDecoration.java @@ -0,0 +1,23 @@ +package ru.yandex.yamblz.ui.adapters; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by Volha on 04.08.2016. + */ +public class FrameItemDecoration extends RecyclerView.ItemDecoration { + + private final int offset = 15; + + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.left = offset; + outRect.right = offset; + outRect.bottom = offset; + outRect.top = offset; + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java new file mode 100644 index 0000000..6b1d735 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java @@ -0,0 +1,133 @@ +package ru.yandex.yamblz.ui.adapters; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.ImageSize; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import butterknife.BindView; +import butterknife.ButterKnife; +import ru.yandex.yamblz.R; +import ru.yandex.yamblz.data.Cover; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +/** + * Created by Volha on 04.08.2016. + */ + +public class GenresRecyclerAdapter extends RecyclerView.Adapter { + + Map> genres = new HashMap<>(); + List keys; + Map subscriptions = new HashMap<>(); + + public GenresRecyclerAdapter() { + } + + public void setItems(Map> genres) { + if ( genres != null ) { + this.genres.clear(); + this.genres.putAll(genres); + keys = new ArrayList<>(genres.keySet()); + } else + keys = new ArrayList<>(); + notifyDataSetChanged(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_genre_item, parent, false); + return new GenreViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if ( holder instanceof GenreViewHolder ) { + GenreViewHolder genre = (GenreViewHolder) holder; + genre.title.setText( keys.get(position) ); + + if (subscriptions.containsKey(genre.cover)) + subscriptions.get(genre.cover).unsubscribe(); + + subscriptions.put(genre.cover, loadCovers(genre.cover, getItemByPosition(position))); + } + } + + private Subscription loadCovers(ImageView itemView, ArrayList covers) { + return rx.Observable + .from(covers) + .observeOn(Schedulers.io()) + .map(cover -> loadImage( cover.getSmall() )) + .subscribeOn(Schedulers.computation()) + .toList() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(bitmaps -> { + itemView.setImageBitmap(makeCollage((ArrayList) bitmaps)); + }); + } + + private Bitmap makeCollage(ArrayList bitmaps) { + if ( bitmaps.size() < 4) + return bitmaps.get(0); + + int columns = 2; + int rows = 2; + + int itemWidth = bitmaps.get(0).getWidth(); + int width = itemWidth * columns; + int itemHeight = bitmaps.get(0).getHeight(); + int height = itemHeight * rows; + + Bitmap collage = Bitmap.createBitmap(width, height, bitmaps.get(0).getConfig()); + Canvas canvas = new Canvas(collage); + for ( int i = 0; i < rows; ++i ) { + for (int j = 0; j < columns; j++) { + Bitmap bitmap = bitmaps.get( i * rows + j ); + canvas.drawBitmap( bitmap, j * itemWidth, i * itemHeight, null ); + } + } + + return collage; + } + + private Bitmap loadImage(String url) { + ImageLoader imageLoader = ImageLoader.getInstance(); + return imageLoader.loadImageSync(url, new ImageSize(100, 100)); + } + + public ArrayList getItemByPosition(int position) { + return genres.get(keys.get(position)); + } + + @Override + public int getItemCount() { + return genres.size(); + } + + static class GenreViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.genre_title) + TextView title; + @BindView(R.id.genre_cover) + ImageView cover; + + public GenreViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index d46490f..0fffb66 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -3,16 +3,90 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import butterknife.BindView; +import butterknife.ButterKnife; import ru.yandex.yamblz.R; +import ru.yandex.yamblz.data.Artist; +import ru.yandex.yamblz.data.Cover; +import ru.yandex.yamblz.retrofit.ApiServices; +import ru.yandex.yamblz.ui.adapters.FrameItemDecoration; +import ru.yandex.yamblz.ui.adapters.GenresRecyclerAdapter; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; public class ContentFragment extends BaseFragment { + + private CompositeSubscription compositeSubscription = new CompositeSubscription(); + + @BindView(R.id.genres) + RecyclerView genresList; + + GenresRecyclerAdapter adapter; + @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_content, container, false); + View rootView = inflater.inflate(R.layout.fragment_content, container, false);; + ButterKnife.bind(this, rootView); + return rootView; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + adapter = new GenresRecyclerAdapter(); + genresList.setItemAnimator(new DefaultItemAnimator()); + genresList.setLayoutManager(new LinearLayoutManager(getContext())); + genresList.addItemDecoration(new FrameItemDecoration()); + genresList.setAdapter(adapter); + downloadAndMapArtists(); + } + + private void downloadAndMapArtists() { + Map> genres = new ConcurrentHashMap<>(); + ApiServices apiServices = new ApiServices(); + + compositeSubscription.add(apiServices + .getArtists() + .flatMapIterable( artists -> artists ) + .doOnNext( artist -> { + for (String genre : artist.getGenres()) { + if ( genres.get(genre) == null || !genre.contains(genre) ) + genres.put( genre, new ArrayList<>() ); + genres.get( genre ).add( artist.getCover() ); + } + } ) + .observeOn( AndroidSchedulers.mainThread() ) + .subscribeOn( Schedulers.newThread() ) + .toList() + .subscribe(artists -> { + adapter.setItems(genres); + }) + ); + } + + @Override + public void onDestroy() { + super.onDestroy(); + compositeSubscription.unsubscribe(); } } diff --git a/app/src/main/res/layout/fragment_content.xml b/app/src/main/res/layout/fragment_content.xml index 81016ea..423f8e5 100644 --- a/app/src/main/res/layout/fragment_content.xml +++ b/app/src/main/res/layout/fragment_content.xml @@ -1,14 +1,13 @@ + android:layout_height="match_parent" + android:background="@color/colorBackground"> - \ No newline at end of file diff --git a/app/src/main/res/layout/list_genre_item.xml b/app/src/main/res/layout/list_genre_item.xml new file mode 100644 index 0000000..9672499 --- /dev/null +++ b/app/src/main/res/layout/list_genre_item.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d106673..4d5e66c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,5 @@ #009688 #00796B #03A9F4 + #cacaca diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 02d4637..571cc37 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,5 +2,7 @@ 16dp 16dp + 200dp + 16dp diff --git a/dependencies.gradle b/dependencies.gradle index d9cb375..4d4d74e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,7 +7,7 @@ ext.versions = [ compileSdk : 23, buildTools : '23.0.3', - androidGradlePlugin : '2.2.0-alpha4', + androidGradlePlugin : '2.1.2', aptGradlePlugin : '1.8', retrolambdaGradlePlugin : '3.2.5', lombokGradlePlugin : '0.2.3.a2', @@ -85,4 +85,14 @@ ext.libraries = [ supportTestRules : "com.android.support.test:rules:$versions.supportTestRunner", espressoCore : "com.android.support.test.espresso:espresso-core:$versions.espresso", espressoContrib : "com.android.support.test.espresso:espresso-contrib:$versions.espresso", + + // + retrofit : "com.squareup.retrofit:retrofit:2.0.0-beta1", + retrofitAdapter : "com.squareup.retrofit:adapter-rxjava:2.0.0-beta1", + jacksonConverter : "com.squareup.retrofit:converter-jackson:2.0.0-beta1", + jacksonDatabind : "com.fasterxml.jackson.core:jackson-databind:2.7.0", + jacksonAnnotations : "com.fasterxml.jackson.core:jackson-annotations:2.7.3", + rxjava : "io.reactivex:rxjava:1.0.14", + rxandroid : "io.reactivex:rxandroid:1.0.1", + universalImageLoader : "com.nostra13.universalimageloader:universal-image-loader:1.9.5", ] diff --git a/gradle.properties b/gradle.properties index 6b6a634..43e9332 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ # Enlarge the gradle heap size. -org.gradle.jvmargs=-Xmx3584m -XX:MaxPermSize=1024m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx1024m -Dfile.encoding=UTF-8 From 3b91268d3691a326f87cd02fa8f039cb33a84bdc Mon Sep 17 00:00:00 2001 From: Tayrinn Date: Fri, 5 Aug 2016 18:47:19 +0300 Subject: [PATCH 2/3] fix bugs, add rx magic --- app/build.gradle | 1 + .../ui/adapters/GenresRecyclerAdapter.java | 106 ++++++++++++++---- .../yamblz/ui/fragments/ContentFragment.java | 81 ++++++++++--- app/src/main/res/layout/list_genre_item.xml | 26 +++-- app/src/main/res/values/strings.xml | 2 + .../yamblz/developer_settings/RxTest.java | 29 +++++ 6 files changed, 202 insertions(+), 43 deletions(-) create mode 100644 app/src/test/java/ru/yandex/yamblz/developer_settings/RxTest.java diff --git a/app/build.gradle b/app/build.gradle index f01a51e..73411d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,6 +86,7 @@ android { dependencies { compile libraries.dagger apt libraries.daggerCompiler + compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0' compile libraries.supportAnnotations compile libraries.supportAppCompat diff --git a/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java index 6b1d735..dd7ef41 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresRecyclerAdapter.java @@ -1,16 +1,25 @@ package ru.yandex.yamblz.ui.adapters; +import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.os.Build; +import android.support.v4.util.ArrayMap; +import android.support.v4.util.LruCache; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.ProgressBar; import android.widget.TextView; import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.ImageSize; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import java.util.ArrayList; import java.util.HashMap; @@ -21,8 +30,11 @@ import butterknife.ButterKnife; import ru.yandex.yamblz.R; import ru.yandex.yamblz.data.Cover; +import rx.Observable; +import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; import rx.schedulers.Schedulers; /** @@ -31,14 +43,15 @@ public class GenresRecyclerAdapter extends RecyclerView.Adapter { - Map> genres = new HashMap<>(); + Map> genres = new HashMap<>(); List keys; - Map subscriptions = new HashMap<>(); + Map subscriptions = new HashMap<>(); + LruCache cache = new LruCache<>(1024 * 10); public GenresRecyclerAdapter() { } - public void setItems(Map> genres) { + public void setItems(Map> genres) { if ( genres != null ) { this.genres.clear(); this.genres.putAll(genres); @@ -59,33 +72,45 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if ( holder instanceof GenreViewHolder ) { GenreViewHolder genre = (GenreViewHolder) holder; genre.title.setText( keys.get(position) ); - - if (subscriptions.containsKey(genre.cover)) - subscriptions.get(genre.cover).unsubscribe(); - - subscriptions.put(genre.cover, loadCovers(genre.cover, getItemByPosition(position))); + genre.progress.setVisibility(View.VISIBLE); + genre.cover.setVisibility(View.GONE); + if (genre.subscription != null) { + genre.subscription.unsubscribe(); + } + if (cache.get(keys.get(position)) != null) { // загружаем из кэша + genre.cover.setImageBitmap(cache.get(keys.get(position))); + genre.cover.setVisibility(View.VISIBLE); + genre.progress.setVisibility(View.GONE); + } + genre.subscription = loadCovers(genre, position); // обновляем картинку всё равно (вдруг изменилась) } } - private Subscription loadCovers(ImageView itemView, ArrayList covers) { + private Subscription loadCovers(GenreViewHolder genre, int position) { return rx.Observable - .from(covers) + .from(getItemByPosition(position)) .observeOn(Schedulers.io()) - .map(cover -> loadImage( cover.getSmall() )) + .flatMap(cover -> loadImage( cover.getSmall() )) + .doOnError(throwable -> { Log.d("loadError", throwable.getMessage()); }) .subscribeOn(Schedulers.computation()) .toList() .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmaps -> { - itemView.setImageBitmap(makeCollage((ArrayList) bitmaps)); + Bitmap collage = makeCollage((ArrayList) bitmaps); + genre.cover.setImageBitmap(collage); + genre.cover.setVisibility(View.VISIBLE); + genre.progress.setVisibility(View.GONE); + cache.put(keys.get(position), collage); }); } private Bitmap makeCollage(ArrayList bitmaps) { - if ( bitmaps.size() < 4) - return bitmaps.get(0); - int columns = 2; - int rows = 2; + if ( bitmaps.size() == 3 ) + return draw3Covers(bitmaps); + + int columns = getColumnCount(bitmaps.size()); + int rows = bitmaps.size() / columns; int itemWidth = bitmaps.get(0).getWidth(); int width = itemWidth * columns; @@ -94,22 +119,56 @@ private Bitmap makeCollage(ArrayList bitmaps) { Bitmap collage = Bitmap.createBitmap(width, height, bitmaps.get(0).getConfig()); Canvas canvas = new Canvas(collage); + for ( int i = 0; i < rows; ++i ) { for (int j = 0; j < columns; j++) { - Bitmap bitmap = bitmaps.get( i * rows + j ); - canvas.drawBitmap( bitmap, j * itemWidth, i * itemHeight, null ); + if ( bitmaps.size() > i*rows + j ) { + Bitmap bitmap = bitmaps.get(i * rows + j); + canvas.drawBitmap(bitmap, j * itemWidth, i * itemHeight, null); + } } } return collage; } - private Bitmap loadImage(String url) { - ImageLoader imageLoader = ImageLoader.getInstance(); - return imageLoader.loadImageSync(url, new ImageSize(100, 100)); + @TargetApi(Build.VERSION_CODES.KITKAT) + private Bitmap draw3Covers(ArrayList bitmaps) { + + int itemWidth = bitmaps.get(0).getWidth(); + int width = itemWidth * 2; + int itemHeight = bitmaps.get(0).getHeight(); + int height = itemHeight * 2; + + Bitmap collage = Bitmap.createBitmap(width, height, bitmaps.get(0).getConfig()); + Canvas canvas = new Canvas(collage); + + canvas.drawBitmap(bitmaps.get(0), 0, 0, null); + canvas.drawBitmap(bitmaps.get(1), 0, itemHeight, null); + bitmaps.set(2, (Bitmap.createScaledBitmap(bitmaps.get(2), itemWidth, height, false)) ); + canvas.drawBitmap(bitmaps.get(2), itemWidth, 0, null); + + return collage; + } + + private int getColumnCount(int size) { + return (int) Math.ceil(Math.sqrt(size)); + } + + private Observable loadImage(String url) { + return Observable.create(subscriber -> { + ImageLoader imageLoader = ImageLoader.getInstance(); + imageLoader.loadImage(url, new ImageSize(100, 100), new SimpleImageLoadingListener() { + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + subscriber.onNext(loadedImage); + subscriber.onCompleted(); + } + }); + }); } - public ArrayList getItemByPosition(int position) { + public List getItemByPosition(int position) { return genres.get(keys.get(position)); } @@ -124,6 +183,9 @@ static class GenreViewHolder extends RecyclerView.ViewHolder { TextView title; @BindView(R.id.genre_cover) ImageView cover; + @BindView((R.id.genre_progress)) + ProgressBar progress; + Subscription subscription; public GenreViewHolder(View itemView) { super(itemView); diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java index 0fffb66..932da7d 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ContentFragment.java @@ -3,22 +3,26 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.content.ContextCompat; +import android.support.v4.util.Pair; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.jakewharton.rxbinding.support.design.widget.RxSnackbar; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Observable; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import butterknife.BindView; import butterknife.ButterKnife; @@ -28,8 +32,12 @@ import ru.yandex.yamblz.retrofit.ApiServices; import ru.yandex.yamblz.ui.adapters.FrameItemDecoration; import ru.yandex.yamblz.ui.adapters.GenresRecyclerAdapter; +import rx.Observable; +import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Func1; +import rx.functions.Func2; +import rx.observables.GroupedObservable; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; @@ -45,7 +53,7 @@ public class ContentFragment extends BaseFragment { @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_content, container, false);; + View rootView = inflater.inflate(R.layout.fragment_content, container, false); ButterKnife.bind(this, rootView); return rootView; } @@ -68,22 +76,67 @@ private void downloadAndMapArtists() { compositeSubscription.add(apiServices .getArtists() .flatMapIterable( artists -> artists ) - .doOnNext( artist -> { - for (String genre : artist.getGenres()) { - if ( genres.get(genre) == null || !genre.contains(genre) ) - genres.put( genre, new ArrayList<>() ); - genres.get( genre ).add( artist.getCover() ); - } - } ) + .flatMap(getGenreArtistPair()) // создаем пару жанр-артист + .groupBy(groupByGenre()) + .flatMap(getGenreCoverList()) // получаем пару жанр - список обложек + .reduce( new HashMap>(), collectAllPairsToMap()) + .retryWhen(errors -> showReloadSnackbar(errors)) .observeOn( AndroidSchedulers.mainThread() ) .subscribeOn( Schedulers.newThread() ) - .toList() .subscribe(artists -> { - adapter.setItems(genres); + adapter.setItems(artists); }) ); } + @NonNull + private Func2>, Pair>, Map>> collectAllPairsToMap() { + return new Func2>, Pair>, Map>>() { + @Override + public Map> call(Map> stringListHashMap, Pair> stringListPair) { + stringListHashMap.put(stringListPair.first, stringListPair.second); + return stringListHashMap; // собираем всё вместе в HashMap + } + }; + } + + private Observable showReloadSnackbar(Observable errors) { + return errors.flatMap( error -> + Observable.create( + subscriber -> Snackbar.make( genresList, R.string.no_data, Snackbar.LENGTH_INDEFINITE ) + .setAction( R.string.reload, v -> { subscriber.onNext(null); subscriber.onCompleted(); }) + .setActionTextColor( ContextCompat.getColor( getContext(), R.color.colorAccent ) ) + .show())); + } + + @NonNull + private Func1>, Observable>>> getGenreCoverList() { + return spgo -> spgo + .map(p-> ((Artist) p.second).getCover()) + .toList() + .map(la-> new Pair>(spgo.getKey(), la)); + } + + @NonNull + private Func1, String> groupByGenre() { + return new Func1, String>() { + @Override + public String call(Pair stringArtistPair) { + return stringArtistPair.first; // группируем по жанрам + } + }; + } + + @NonNull + private Func1>> getGenreArtistPair() { + return new Func1>>() { + @Override + public Observable> call(Artist artist) { + return Observable.from(artist.getGenres()).map(s -> new Pair(s, artist)); + } + }; + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/res/layout/list_genre_item.xml b/app/src/main/res/layout/list_genre_item.xml index 9672499..02df7fb 100644 --- a/app/src/main/res/layout/list_genre_item.xml +++ b/app/src/main/res/layout/list_genre_item.xml @@ -1,27 +1,39 @@ + android:layout_height="wrap_content" + android:orientation="horizontal"> - + android:layout_height="@dimen/genre_image_dimen"> + + + + + + android:layout_gravity="center_vertical" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7474f04..4ee4e19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Yamblz Hello + Обновить + Не удалось загрузить данные diff --git a/app/src/test/java/ru/yandex/yamblz/developer_settings/RxTest.java b/app/src/test/java/ru/yandex/yamblz/developer_settings/RxTest.java new file mode 100644 index 0000000..3fbc4be --- /dev/null +++ b/app/src/test/java/ru/yandex/yamblz/developer_settings/RxTest.java @@ -0,0 +1,29 @@ +package ru.yandex.yamblz.developer_settings; + +import android.provider.Settings; +import android.util.Log; + +import org.junit.Test; + +import rx.Observable; + +/** + * Created by Volha on 05.08.2016. + */ + +public class RxTest { + + @Test + public void rxFilterTest() { + + Observable.range(0, 10) + .flatMap( i -> { + if ( i % 2 == 0) + return Observable.just(i); + else + return Observable.empty(); + }) + .subscribe(it -> System.out.println(it)); + } + +} From 34d86645360db2b5ae8363612452456960564b4d Mon Sep 17 00:00:00 2001 From: Tayrinn Date: Mon, 8 Aug 2016 12:23:38 +0300 Subject: [PATCH 3/3] fix download url --- app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java b/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java index 142eea5..93562b7 100644 --- a/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java +++ b/app/src/main/java/ru/yandex/yamblz/retrofit/ApiServices.java @@ -18,7 +18,7 @@ public class ApiServices { - public final static String API_URL = "http://cache-default04e.cdn.yandex.net/download.cdn.yandex.net/"; + public final static String API_URL = "http://download.cdn.yandex.net/"; private final ApiWebService webService;