diff --git a/app/build.gradle b/app/build.gradle index 445372a..c9b0998 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,8 @@ android { applicationId 'ru.yandex.yamblz' minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk - versionCode versions.code // Notice that you may want to use BUILD_NUMBER from CI in real project with own CI. + versionCode versions.code + // Notice that you may want to use BUILD_NUMBER from CI in real project with own CI. versionName versions.name // Make generated apks contain version name and code. @@ -121,6 +122,23 @@ dependencies { exclude module: 'recyclerview-v7' exclude module: 'support-v4' } + + // Web requests + compile 'com.squareup.retrofit2:retrofit:2.1.0' + compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' + compile 'com.squareup.okhttp3:logging-interceptor:3.3.0' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' + + // Json parsing + compile 'com.google.code.gson:gson:2.7' + compile 'me.tatarka:auto-parcel-gson:0.1' + apt 'me.tatarka:auto-parcel-gson-processor:0.1' + + //RX <3 + compile 'io.reactivex:rxandroid:1.2.1' + compile 'io.reactivex:rxjava:1.1.6' + + compile 'com.github.bumptech.glide:glide:3.7.0' } configurations.all { diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index e5f9972..ea7d6ab 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -2,12 +2,15 @@ import android.app.Application; import android.content.Context; +import android.os.Looper; import android.support.annotation.NonNull; import ru.yandex.yamblz.developer_settings.DevMetricsProxy; import ru.yandex.yamblz.developer_settings.DeveloperSettingsModel; import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.handler.DisableScrollLoadingHandler; import ru.yandex.yamblz.loader.CollageLoaderManager; +import ru.yandex.yamblz.loader.ParallelCollageLoader; import timber.log.Timber; public class App extends Application { @@ -34,8 +37,8 @@ public void onCreate() { devMetricsProxy.apply(); } - CollageLoaderManager.init(null); // add implementation - CriticalSectionsManager.init(null); // add implementation + CollageLoaderManager.init(new ParallelCollageLoader()); + CriticalSectionsManager.init(new DisableScrollLoadingHandler(Looper.myQueue())); // add implementation } @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..aff3dc6 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/Artist.java @@ -0,0 +1,36 @@ +package ru.yandex.yamblz.data; + +import java.util.List; + +public class Artist { + public final int id; + public final String name; + public final List genres; + private final int tracks; + private final int albums; + private final String link; + private final String description; + final Cover cover; + + public Artist(int id, String name, List genres, int tracks, int albums, String link, String description, Cover cover) { + this.id = id; + this.name = name; + this.genres = genres; + this.tracks = tracks; + this.albums = albums; + this.link = link; + this.description = description; + this.cover = cover; + } + + static class Cover { + final String small; + final String big; + + public Cover(String small, String big) { + this.small = small; + this.big = big; + } + } + +} diff --git a/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java b/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java new file mode 100644 index 0000000..7a38946 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/ArtistsApi.java @@ -0,0 +1,11 @@ +package ru.yandex.yamblz.data; + +import java.util.List; + +import retrofit2.http.GET; +import rx.Single; + +public interface ArtistsApi { + @GET("artists.json") + Single> getArtists(); +} diff --git a/app/src/main/java/ru/yandex/yamblz/data/Genre.java b/app/src/main/java/ru/yandex/yamblz/data/Genre.java new file mode 100644 index 0000000..6bd02e9 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/data/Genre.java @@ -0,0 +1,28 @@ +package ru.yandex.yamblz.data; + +import java.util.ArrayList; +import java.util.List; + +public class Genre { + private final String name; + private final List artists; + + public Genre(String name, List artists) { + this.name = name; + this.artists = artists; + } + + public String getName() { + return name; + } + + public List getCollageUrls() { + List urls = new ArrayList<>(); + for (Artist artist : artists) { + urls.add(artist.cover.small); + if (urls.size() >= 4) return urls; + } + + return urls; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java index 8b8cc56..68edf64 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java @@ -1,6 +1,6 @@ package ru.yandex.yamblz.handler; -public interface CriticalSectionsHandler { +public interface CriticalSectionsHandler { void startSection(int id); diff --git a/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java new file mode 100644 index 0000000..ec79bde --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/handler/DisableScrollLoadingHandler.java @@ -0,0 +1,90 @@ +package ru.yandex.yamblz.handler; + +import android.os.Handler; +import android.os.Looper; +import android.os.MessageQueue; + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class DisableScrollLoadingHandler implements CriticalSectionsHandler, MessageQueue.IdleHandler { + private final Set runningSections = Collections.newSetFromMap(new ConcurrentHashMap()); + private final List tasks = new CopyOnWriteArrayList<>(); + private final WeakReference listenableQueue; + + public DisableScrollLoadingHandler(MessageQueue queue) { + listenableQueue = new WeakReference<>(queue); + } + + @Override + public void startSection(int id) { + runningSections.add(id); + } + + @Override + public void stopSection(int id) { + if (runningSections.contains(id)) { + runningSections.remove(id); + } + addIdleHandler(); + } + + @Override + public void stopSections() { + runningSections.clear(); + addIdleHandler(); + } + + private void runTasks() { + for (Task task : tasks) { + if (runningSections.size() == 0) { + task.run(); + removeLowPriorityTask(task); + } + } + } + + // Вот тут я хз, честно. + // Вот вроде по дяде Бобу это неправильно написано, а с другой стороны вроде логично выглядит + private boolean addIdleHandler() { + if (listenableQueue.get() != null) { + if (tasks.size() != 0 && !queueIdle()) { + listenableQueue.get().addIdleHandler(this); + } + return true; + } + + return false; + } + + @Override + public void postLowPriorityTask(Task task) { + if (addIdleHandler()) tasks.add(task); + // throw exception? + } + + @Override + public void postLowPriorityTaskDelayed(Task task, int delay) { + new Handler(Looper.getMainLooper()).postDelayed(() -> postLowPriorityTask(task), delay); + } + + @Override + public void removeLowPriorityTask(Task task) { + tasks.remove(task); + } + + @Override + public void removeLowPriorityTasks() { + tasks.clear(); + } + + @Override + public boolean queueIdle() { + if (runningSections.size() == 0) runTasks(); + return runningSections.size() == 0 && tasks.size() != 0; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java index dad5b76..c3124ba 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/CollageLoader.java @@ -14,4 +14,5 @@ public interface CollageLoader { void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy); + void destroyAll(); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java index d81d52d..9ad29e3 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java @@ -4,7 +4,7 @@ import java.util.List; -public interface CollageStrategy { +interface CollageStrategy { Bitmap create(List bitmaps); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java b/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java index 9a8be2b..01ca5a0 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java @@ -2,7 +2,7 @@ import android.graphics.Bitmap; -public interface ImageTarget { +interface ImageTarget { void onLoadBitmap(Bitmap bitmap); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java new file mode 100644 index 0000000..c096647 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/loader/ParallelCollageLoader.java @@ -0,0 +1,91 @@ +package ru.yandex.yamblz.loader; + +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import android.util.LruCache; +import android.widget.ImageView; + +import java.util.List; +import java.util.WeakHashMap; + +import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.utils.Utils; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; +import timber.log.Timber; + + +public class ParallelCollageLoader implements CollageLoader { + @NonNull private final WeakHashMap subscriptionTargets; + @NonNull private final CompositeSubscription subs; + @NonNull private final CollageStrategy collageStrategy; + @NonNull private final LruCache, Bitmap> memoryCache; + + public ParallelCollageLoader() { + subscriptionTargets = new WeakHashMap<>(); + subs = new CompositeSubscription(); + collageStrategy = new SimpleCollageStrategy(); + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + memoryCache = new LruCache<>(cacheSize); + } + + @Override + public void loadCollage(List urls, ImageView imageView) { + loadCollage(urls, imageView, this.collageStrategy); + } + + @Override + public void loadCollage(List urls, ImageTarget imageTarget) { + loadCollage(urls, imageTarget, this.collageStrategy); + } + + @Override + public void loadCollage(List urls, final ImageView imageView, CollageStrategy collageStrategy) { + ImageTarget target = bitmap -> { + imageView.setAlpha(0f); + imageView.setImageBitmap(bitmap); + imageView.animate().alpha(1); + }; + loadCollage(urls, target, collageStrategy); + } + + private void loadBitmap(ImageTarget imageTarget, Bitmap cachedCollage) { + CriticalSectionsManager.getHandler().postLowPriorityTask(() -> imageTarget.onLoadBitmap(cachedCollage)); + } + + @Override + public void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy) { + Subscription s = subscriptionTargets.get(imageTarget); + if (s != null) { + subs.remove(s); + } + Bitmap cachedCollage = memoryCache.get(urls); + if (cachedCollage != null) { + Timber.d("Cache hit! Adding task to handler"); + loadBitmap(imageTarget, cachedCollage); + } else { + Subscription subscription = Observable.from(urls) + .flatMap(url -> Utils.loadBitmapAsync(url).subscribeOn(Schedulers.io())) + .toList() + .map(collageStrategy::create) + .doOnNext(collage -> memoryCache.put(urls, collage)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(collage -> loadBitmap(imageTarget, collage), + Throwable::printStackTrace + ); + subs.add(subscription); + subscriptionTargets.put(imageTarget, subscription); + } + } + + @Override + public void destroyAll() { + subs.clear(); + subscriptionTargets.clear(); + } + +} diff --git a/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java new file mode 100644 index 0000000..299d62f --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/loader/SimpleCollageStrategy.java @@ -0,0 +1,48 @@ +package ru.yandex.yamblz.loader; + +import android.graphics.Bitmap; +import android.graphics.Canvas; + +import java.util.List; + +class SimpleCollageStrategy implements CollageStrategy { + @Override + public Bitmap create(List bitmaps) { + return combineBitmaps(bitmaps); + } + + private Bitmap combineBitmaps(List bitmaps) { + int width = 300, height = 300; + Bitmap combinedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + Canvas comboImage = new Canvas(combinedBitmap); + Bitmap scaledBitmap; + + switch (bitmaps.size()) { + case 1: + return bitmaps.get(0); + case 2: + scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(0), width, height, true); + comboImage.drawBitmap(scaledBitmap, 0f, 0f, null); + scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(1), width, height, true); + comboImage.drawBitmap(scaledBitmap, width / 2, 0f, null); + break; + case 3: + scaledBitmap = Bitmap.createScaledBitmap(bitmaps.get(0), width, height, true); + comboImage.drawBitmap(scaledBitmap, 0f, 0f, null); + comboImage.drawBitmap(bitmaps.get(1), width / 2, 0f, null); + comboImage.drawBitmap(bitmaps.get(2), width / 2, height / 2, null); + break; + default: + case 4: + comboImage.drawBitmap(bitmaps.get(0), 0f, 0f, null); + comboImage.drawBitmap(bitmaps.get(1), 0f, height / 2, null); + comboImage.drawBitmap(bitmaps.get(2), width / 2, 0f, null); + comboImage.drawBitmap(bitmaps.get(3), width / 2, height / 2, null); + break; + } + + return combinedBitmap; + + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java b/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java index 1dc051b..a0d6dba 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java @@ -28,4 +28,9 @@ public void loadCollage(List urls, ImageTarget imageTarget, } + @Override + public void destroyAll() { + + } + } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java new file mode 100644 index 0000000..018c1f4 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/ArtistsLoadingPresenter.java @@ -0,0 +1,68 @@ +package ru.yandex.yamblz.ui.fragments; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import ru.yandex.yamblz.data.Artist; +import ru.yandex.yamblz.data.ArtistsApi; +import ru.yandex.yamblz.data.Genre; +import ru.yandex.yamblz.ui.presenters.Presenter; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; + + +class ArtistsLoadingPresenter extends Presenter { + + @NonNull private final CompositeSubscription subs = new CompositeSubscription(); + @NonNull private final ArtistsApi artistsApi; + + ArtistsLoadingPresenter(@NonNull ArtistsApi artistsApi) { + this.artistsApi = artistsApi; + } + + void loadArtists() { + subs.add(artistsApi.getArtists() + .subscribeOn(Schedulers.io()) + .map(this::extractGenres) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::showContent, + Throwable::printStackTrace)); + } + + private void showContent(List list) { + ContentFragment view = view(); + if (view != null) view.showContent(list); + } + + private List extractGenres(List artistsList) { + ConcurrentHashMap> genres = new ConcurrentHashMap<>(); + List genreList = new ArrayList<>(); + + for (Artist artist : artistsList) { + for (String genreName : artist.genres) { + if (genres.containsKey(genreName)) { + genres.get(genreName).add(artist); + } else { + if (genreName != null && !genreName.equals("")) { + genres.put(genreName, new ArrayList() {{ + add(artist); + }}); + + } + } + } + } + + for (Map.Entry> entry : genres.entrySet()) { + genreList.add(new Genre(entry.getKey(), entry.getValue())); + } + + return genreList; + + } +} 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..6b4ab41 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,116 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.OnScrollListener; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import java.util.List; + +import butterknife.BindView; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; import ru.yandex.yamblz.R; +import ru.yandex.yamblz.data.ArtistsApi; +import ru.yandex.yamblz.data.Genre; +import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.loader.CollageLoaderManager; +import timber.log.Timber; + +import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING; +import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE; public class ContentFragment extends BaseFragment { + @BindView(R.id.main_recycler) + RecyclerView recycler; + + private ArtistsLoadingPresenter presenter; + private GenreAdapter adapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // TODO: Use dagger + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient client = new OkHttpClient.Builder() + .build(); + + String baseUrl = getString(R.string.base_url); + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(baseUrl) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build(); + + ArtistsApi artistsApi = retrofit.create(ArtistsApi.class); + + presenter = new ArtistsLoadingPresenter(artistsApi); + presenter.bindView(this); + Timber.d("Loading artists"); + presenter.loadArtists(); + } + @NonNull @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_content, container, false); } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + recycler.setLayoutManager(new LinearLayoutManager(getContext())); + adapter = new GenreAdapter(); + recycler.setAdapter(adapter); + recycler.addOnScrollListener(scrollingListener); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + CollageLoaderManager.getLoader().destroyAll(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + presenter.unbindView(this); + } + + void showContent(List genreList) { + adapter.setContent(genreList); + } + + private final OnScrollListener scrollingListener = new OnScrollListener() { + boolean scrollingStarted; + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + switch (newState) { + case SCROLL_STATE_IDLE: + Timber.d("Stopped scrolling"); + CriticalSectionsManager.getHandler().stopSection(0); + scrollingStarted = false; + break; + case SCROLL_STATE_DRAGGING: + if (!scrollingStarted) { + Timber.d("Started scrolling"); + CriticalSectionsManager.getHandler().startSection(0); + scrollingStarted = true; + } + break; + } + } + }; } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java new file mode 100644 index 0000000..fe497cd --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/GenreAdapter.java @@ -0,0 +1,100 @@ +package ru.yandex.yamblz.ui.fragments; + +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 java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import ru.yandex.yamblz.R; +import ru.yandex.yamblz.data.Genre; +import ru.yandex.yamblz.loader.CollageLoader; +import ru.yandex.yamblz.loader.CollageLoaderManager; + +class GenreAdapter extends RecyclerView.Adapter { + private final static int PLACEHOLDER = 0; + private final static int GENRE = 1; + private final CollageLoader imagesLoader; + private final List genres = new ArrayList<>(); + + GenreAdapter() { + imagesLoader = CollageLoaderManager.getLoader(); + } + + void setContent(List genreList) { + if (genreList != null && genreList.size() != 0) { + this.genres.addAll(genreList); + } + + notifyDataSetChanged(); + } + + @Override + public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { + return true; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case PLACEHOLDER: + View placeholder = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_placeholder, parent, false); + return new ProgressHolder(placeholder); + case GENRE: + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_artists_genre, parent, false); + return new GenreHolder(view); + } + + return null; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof GenreHolder) { + GenreHolder genreHolder = (GenreHolder) holder; + genreHolder.genre.setText(genres.get(position).getName()); + genreHolder.image.setImageResource(R.color.colorGray); + imagesLoader.loadCollage(genres.get(position).getCollageUrls(), genreHolder.image); + } + } + + @Override + public int getItemCount() { + return isEmpty() ? 1 : genres.size(); + } + + private boolean isEmpty() { + return genres.size() == 0; + } + + @Override + public int getItemViewType(int position) { + if (isEmpty() && position == 0) return PLACEHOLDER; + return GENRE; + } + + + static class GenreHolder extends RecyclerView.ViewHolder { + @BindView(R.id.item_image) + ImageView image; + @BindView(R.id.item_genres) + TextView genre; + + GenreHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + private static class ProgressHolder extends RecyclerView.ViewHolder { + ProgressHolder(View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/utils/Utils.java b/app/src/main/java/ru/yandex/yamblz/utils/Utils.java new file mode 100644 index 0000000..c7f729d --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/utils/Utils.java @@ -0,0 +1,43 @@ +package ru.yandex.yamblz.utils; + + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import java.io.IOException; +import java.io.InputStream; + +import rx.Observable; +import timber.log.Timber; + +public class Utils { + private Utils() { + + } + + public static Observable loadBitmapAsync(String urlString) { + return Observable.fromCallable(() -> { + Timber.d("Loading image on thread " + Thread.currentThread().getName()); + InputStream in = null; + try { + in = new java.net.URL(urlString).openStream(); + return BitmapFactory.decodeStream(in); + } catch (Exception e) { + e.printStackTrace(); + } finally { + closeConnection(in); + } + return null; + }); + } + + + private static void closeConnection(InputStream in) { + try { + if (in != null) in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/res/layout/fragment_content.xml b/app/src/main/res/layout/fragment_content.xml index 81016ea..a3fc809 100644 --- a/app/src/main/res/layout/fragment_content.xml +++ b/app/src/main/res/layout/fragment_content.xml @@ -3,12 +3,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:id="@+id/main_recycler" + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_artists_genre.xml b/app/src/main/res/layout/item_artists_genre.xml new file mode 100644 index 0000000..e196a24 --- /dev/null +++ b/app/src/main/res/layout/item_artists_genre.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_placeholder.xml b/app/src/main/res/layout/item_placeholder.xml new file mode 100644 index 0000000..baa665b --- /dev/null +++ b/app/src/main/res/layout/item_placeholder.xml @@ -0,0 +1,20 @@ + + + + + + + + \ 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..86172b5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,5 +2,7 @@ #009688 #00796B - #03A9F4 + #88bbd2 + #b1b1b1 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7474f04..bcff4bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Yamblz - Hello + Wait, please… + http://cache-default03g.cdn.yandex.net/download.cdn.yandex.net/mobilization-2016/ + Artist image diff --git a/dependencies.gradle b/dependencies.gradle index d9cb375..098134d 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.2.0-alpha7', aptGradlePlugin : '1.8', retrolambdaGradlePlugin : '3.2.5', lombokGradlePlugin : '0.2.3.a2', @@ -36,7 +36,9 @@ ext.versions = [ assertJ : '2.4.0', equalsVerifier : '1.7.5', mockito : '1.10.19', - supportTestRunner : '0.5', + supportTestRunner : '0.5' + + ] ext.gradlePlugins = [ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 86a3e94..38580b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 30 20:17:48 ICT 2016 +#Wed Aug 03 09:56:42 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip