diff --git a/app/build.gradle b/app/build.gradle index 445372a..f47577a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -92,11 +92,14 @@ dependencies { compile libraries.supportDesign compile libraries.supportRecyclerView compile libraries.supportCardView + compile libraries.rx compile libraries.butterKnife apt libraries.butterKnifeCompiler compile libraries.timber + compile libraries.rx + compile libraries.rxAndroid // Developer tools (Developer Settings) compile libraries.stetho diff --git a/app/src/main/java/ru/yandex/yamblz/App.java b/app/src/main/java/ru/yandex/yamblz/App.java index e5f9972..cb61db4 100644 --- a/app/src/main/java/ru/yandex/yamblz/App.java +++ b/app/src/main/java/ru/yandex/yamblz/App.java @@ -4,9 +4,11 @@ import android.content.Context; import android.support.annotation.NonNull; +import ru.yandex.yamblz.artists.utils.DataSingleton; 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.StubCriticalSectionsHandler; import ru.yandex.yamblz.loader.CollageLoaderManager; import timber.log.Timber; @@ -23,7 +25,6 @@ public static App get(@NonNull Context context) { public void onCreate() { super.onCreate(); applicationComponent = prepareApplicationComponent().build(); - if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); @@ -35,7 +36,8 @@ public void onCreate() { } CollageLoaderManager.init(null); // add implementation - CriticalSectionsManager.init(null); // add implementation + CriticalSectionsManager.init(new StubCriticalSectionsHandler()); // add implementation + DataSingleton.init(this); } @NonNull diff --git a/app/src/main/java/ru/yandex/yamblz/artists/ArtistModel.java b/app/src/main/java/ru/yandex/yamblz/artists/ArtistModel.java new file mode 100644 index 0000000..816918e --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/artists/ArtistModel.java @@ -0,0 +1,63 @@ +package ru.yandex.yamblz.artists; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +//Модель артиста,генерируется из jsonObject +public class ArtistModel { + protected JSONObject json; + public long id; + public String name; + public String smallImageUrl; + public String bigImageUrl; + public ArrayList genres; + public int tracks; + public int albums; + public String link; + public String description; + + public ArtistModel(){ + genres=new ArrayList<>(); + } + + public ArtistModel(JSONObject artist){ + this(); + changeData(artist); + } + + public void changeData(JSONObject json){ + this.json = json; + genres.clear(); + try { + id=json.getLong("id"); + name=json.getString("name"); + JSONArray genresJsonArray=json.getJSONArray("genres"); + for (int i = 0; i < genresJsonArray.length(); i++) { + genres.add(genresJsonArray.getString(i)); + } + smallImageUrl=json.getJSONObject("cover").getString("small"); + bigImageUrl=json.getJSONObject("cover").getString("big"); + tracks=json.getInt("tracks"); + albums=json.getInt("albums"); + if(json.has("link")){ + link=json.getString("link"); + }else{ + link=""; + } + + description=json.getString("description"); + if(!description.isEmpty()){//игнорируем пустые строки + description=Character.toUpperCase(description.charAt(0))+description.substring(1,description.length()); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject getJson() { + return json; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/artists/DataLoadingFragment.java b/app/src/main/java/ru/yandex/yamblz/artists/DataLoadingFragment.java new file mode 100644 index 0000000..4f499ba --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/artists/DataLoadingFragment.java @@ -0,0 +1,25 @@ +package ru.yandex.yamblz.artists; + +import android.os.Bundle; +import android.support.v4.app.Fragment; + + +//retain fragment для загрузки данные,не имеет ui,нужен только +//для сохранения и доступа к DataLoadingModel +public class DataLoadingFragment extends Fragment { + private final DataLoadingModel dataLoadingModel; + + public DataLoadingFragment() { + dataLoadingModel = new DataLoadingModel(); + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + public DataLoadingModel getDataLoadingModel() { + return dataLoadingModel; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/artists/DataLoadingModel.java b/app/src/main/java/ru/yandex/yamblz/artists/DataLoadingModel.java new file mode 100644 index 0000000..6dfb56c --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/artists/DataLoadingModel.java @@ -0,0 +1,156 @@ +package ru.yandex.yamblz.artists; + +import android.database.Observable; +import android.os.AsyncTask; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import ru.yandex.yamblz.artists.utils.DataSingleton; + +/*модель для асинхронной загрузки данных. +Можно подписатся на события:начала загрузки,ошибки,успеха +//при добавление слушателя,если данные сенйчас грузятся, +у слушателя вызавется onLoadingStart + */ +public class DataLoadingModel { + + + private static final String TAG = "DataLoadingModel"; + + private final DataLoadingObservable mObservable = new DataLoadingObservable(); + private LoadAsyncTask loadingTask; + private boolean isWorking; + + public DataLoadingModel() { + } + + public void loadData() { + if (isWorking) { + return; + } + mObservable.notifyStarted(); + isWorking = true; + loadingTask = new LoadAsyncTask(); + loadingTask.execute(); + } + + public void stopLoadData() { + if (isWorking) { + loadingTask.cancel(true); + isWorking = false; + } + } + + public void registerObserver(final Observer observer) { + mObservable.registerObserver(observer); + if (isWorking) { + observer.onLoadingStart(this); + } + } + + public void unregisterObserver(final Observer observer) { + mObservable.unregisterObserver(observer); + } + + public interface Observer { + void onLoadingStart(DataLoadingModel signInModel); + + void onLoadingSucceeded(DataLoadingModel signInModel); + + void onLoadingFailed(DataLoadingModel signInModel); + } + + private class DataLoadingObservable extends Observable { + public void notifyStarted() { + for (final Observer observer : mObservers) { + observer.onLoadingStart(DataLoadingModel.this); + } + } + + public void notifySucceeded() { + for (final Observer observer : mObservers) { + observer.onLoadingSucceeded(DataLoadingModel.this); + } + } + + public void notifyFailed() { + for (final Observer observer : mObservers) { + observer.onLoadingFailed(DataLoadingModel.this); + } + } + } + private class LoadAsyncTask extends AsyncTask { + + protected String loadArtistsFromWeb() { + URL url = null; + BufferedReader reader = null; + String result=null; + try { + url = new URL("http://download.cdn.yandex.net/mobilization-2016/artists.json"); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setRequestMethod("GET"); + urlConnection.connect(); + InputStream inputStream = urlConnection.getInputStream(); + StringBuilder buffer = new StringBuilder(); + reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null) { + buffer.append(line); + } + result=buffer.toString(); + } catch (IOException e) { + e.printStackTrace(); + } + finally { + if(reader!=null) try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return result; + + } + + protected boolean readArtistJson() { + if (DataSingleton.get().hasData()) { + Log.i(TAG, "already loaded"); + return true; + } else { + String jsonString = loadArtistsFromWeb(); + if (jsonString == null) { + Log.i(TAG, "failed to load data"); + return false; + } else { + DataSingleton.get().setData(jsonString); + return true; + } + } + + } + + @Override + protected Boolean doInBackground(Void... voids) { + return readArtistJson(); + } + + @Override + protected void onPostExecute(Boolean success) { + super.onPostExecute(success); + isWorking = false; + if (success) { + Log.i(TAG,"successfully get data"); + mObservable.notifySucceeded(); + } else { + Log.i(TAG,"error while get data"); + mObservable.notifyFailed(); + } + } + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/artists/DefaultLoadingObserver.java b/app/src/main/java/ru/yandex/yamblz/artists/DefaultLoadingObserver.java new file mode 100644 index 0000000..30481db --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/artists/DefaultLoadingObserver.java @@ -0,0 +1,47 @@ +package ru.yandex.yamblz.artists; + +import android.app.ProgressDialog; +import android.content.Context; +import android.util.Log; + +public class DefaultLoadingObserver implements DataLoadingModel.Observer { + + private ProgressDialog progressDialog; + private Context context; + private static final String TAG = "DefaultLoadingObserver"; + + public DefaultLoadingObserver(Context context){ + this.context=context; + } + + @Override + public void onLoadingStart(DataLoadingModel loadingModel) { + if(progressDialog==null){ + progressDialog = new ProgressDialog(context); + } + progressDialog.setCancelable(false); + progressDialog.setMessage("Loading..."); + progressDialog.show(); + Log.i(TAG, "start getting data"); + } + + @Override + public void onLoadingSucceeded(DataLoadingModel loadingModel) { + Log.i(TAG, "successfully get data"); + progressDialog.dismiss(); + dispose(); + + } + + @Override + public void onLoadingFailed(DataLoadingModel loadingModel) { + progressDialog.dismiss(); + Log.i(TAG, "failed get data"); + dispose(); + } + + public void dispose(){ + if(progressDialog!=null)progressDialog.dismiss(); + } + +} diff --git a/app/src/main/java/ru/yandex/yamblz/artists/utils/CacheHelper.java b/app/src/main/java/ru/yandex/yamblz/artists/utils/CacheHelper.java new file mode 100644 index 0000000..be1cdea --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/artists/utils/CacheHelper.java @@ -0,0 +1,70 @@ +package ru.yandex.yamblz.artists.utils; + +import android.content.Context; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; + +//класс для сохранения,получения и удаления строк в файл(кеширования) +public class CacheHelper { + private static final String TAG="CacheHelper"; + private static final String directory="/CachedStrings/"; + + public static void cacheString(Context context,String key,String mJsonResponse) { + try { + File checkFile = new File(context.getApplicationInfo().dataDir + directory); + if (!checkFile.exists()) { + checkFile.mkdir(); + } + FileWriter file = new FileWriter(checkFile.getAbsolutePath()+ key); + file.write(mJsonResponse); + file.flush(); + file.close(); + Log.i(TAG,"cache write key:"+key); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String readCacheString(Context context,String key) { + try { + File checkFile = new File(context.getApplicationInfo().dataDir + directory); + File f = new File(checkFile.getAbsolutePath()+ key); + if (!f.exists()) { + return null; + } + FileInputStream is = new FileInputStream(f); + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + is.close(); + Log.i(TAG, "cache read key:" + key); + return new String(buffer); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static void clearCache(Context context) { + File checkFile = new File(context.getApplicationInfo().dataDir + directory); + File f = new File(checkFile.getAbsolutePath()); + File[] files = f.listFiles(); + for (File fInDir : files) { + fInDir.delete(); + } + Log.i(TAG,"clear cache"); + } + + public static void deleteFile(Context context,String key) { + File checkFile = new File(context.getApplicationInfo().dataDir + directory); + File f = new File(checkFile.getAbsolutePath()+ key); + if (f.exists()) { + f.delete(); + } + Log.i(TAG,"delete cache key:"+key); + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/artists/utils/DataSingleton.java b/app/src/main/java/ru/yandex/yamblz/artists/utils/DataSingleton.java new file mode 100644 index 0000000..27368d2 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/artists/utils/DataSingleton.java @@ -0,0 +1,112 @@ +package ru.yandex.yamblz.artists.utils; + +import android.content.Context; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import ru.yandex.yamblz.artists.ArtistModel; + +//Singleton для получения данных об артистах из любого места приложения + +public class DataSingleton { + private static final String TAG="DataSingleton"; + private static final String ARTIST_JSON_KEY = "cachedArtists"; + private static DataSingleton dataSingleton; + private Context context; + private List artists; + private HashMap> artistsByGenre; + + private DataSingleton(Context context){ + this.context=context; + String jsonString=CacheHelper.readCacheString(context, ARTIST_JSON_KEY); + if(jsonString!=null){ + artists=parseData(jsonString); + Log.i(TAG,"get data from cache"); + } + } + + private List parseData(String json){ + CacheHelper.cacheString(context, ARTIST_JSON_KEY,json); + Log.i(TAG, "save artists to cache"); + + try { + List artist = new ArrayList<>(); + JSONArray artistList = new JSONArray(json); + artistsByGenre=new HashMap<>(); + for (int i = 0; i < artistList.length(); i++) { + ArtistModel artistModel=new ArtistModel(artistList.getJSONObject(i)); + artist.add(artistModel); + for(String genre:artistModel.genres){ + List artistGenreList; + if(artistsByGenre.containsKey(genre)){ + artistGenreList=artistsByGenre.get(genre); + }else{ + artistGenreList=new ArrayList<>(); + artistsByGenre.put(genre,artistGenreList); + } + artistGenreList.add(artistModel); + } + } + Collections.sort(artist, (lhs, rhs) -> lhs.name.compareTo(rhs.name)); + return artist; + + } catch (JSONException e) { + e.printStackTrace(); + throw new RuntimeException("bad json data"); + } + } + + public static void init(Context context){ + if(dataSingleton!=null){ + throw new RuntimeException("singleton must be init once"); + }else{ + dataSingleton=new DataSingleton(context); + } + } + + public void setData(String json){ + artists=parseData(json); + } + + public boolean hasData(){ + return artists!=null; + } + + public List getArtists() { + return artists; + } + + public static DataSingleton get(){ + return dataSingleton; + } + + public static void dispose(){ + dataSingleton=null; + } + + public List getGenres() { + return new ArrayList<>(artistsByGenre.keySet()); + } + + public List getImagesForGenre(String genre){ + int index=getGenres().indexOf(genre); + List artists=getArtistsByGenre(genre); + if(artists==null)return null; + List urls=new ArrayList<>(); + for(ArtistModel artistModel:artists){ + urls.add(artistModel.smallImageUrl); + } + return urls; + } + + public List getArtistsByGenre(String genre){ + return artistsByGenre.get(genre); + } +} 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..fcf3f7e 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/CriticalSectionsHandler.java @@ -2,6 +2,7 @@ public interface CriticalSectionsHandler { + //пока работает секция ло не работают void startSection(int id); void stopSection(int id); diff --git a/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java b/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java index 0af9646..eed7897 100644 --- a/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java +++ b/app/src/main/java/ru/yandex/yamblz/handler/StubCriticalSectionsHandler.java @@ -1,39 +1,216 @@ package ru.yandex.yamblz.handler; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + public class StubCriticalSectionsHandler implements CriticalSectionsHandler { + private static final String TAG = "StubCritSectHandler"; + private final MyHandler mainHandler; + private Set sparseArray; + + public StubCriticalSectionsHandler() { + sparseArray = new HashSet<>(); + mainHandler = new MyHandler(Looper.getMainLooper()); + } @Override public void startSection(int id) { - + sparseArray.add(id); + checkSections(); } @Override public void stopSection(int id) { - + sparseArray.remove(id); + checkSections(); } @Override public void stopSections() { + sparseArray.clear(); + checkSections(); + } + + private void checkSections() { + if (sparseArray.size() == 0) { + Log.d(TAG, "start mainHandler"); + mainHandler.resume(); + } else { + Log.d(TAG, "stop mainHandler"); + mainHandler.pause(); + } } @Override public void postLowPriorityTask(Task task) { - + mainHandler.postDelayedTask(task,0); } @Override public void postLowPriorityTaskDelayed(Task task, int delay) { - + mainHandler.postDelayedTask(task,delay); } @Override public void removeLowPriorityTask(Task task) { - + mainHandler.removeTask(task); } @Override public void removeLowPriorityTasks() { + mainHandler.removeTasks(); + } + + /* + Хендлеры зранят ссылки на TaskRunnable что-бы была возможность отменить таски + */ + + private static class MyHandler extends Handler { + private final DelayedHandler delayedHandler; + private List postRunnables; + private boolean isRunning = true; + + MyHandler(Looper looper) { + super(looper); + delayedHandler=new DelayedHandler(getLooper()); + postRunnables = new ArrayList<>(); + } + + + @Override + public void dispatchMessage(Message msg) { + if (!isRunning) { + if (msg.getCallback() == null) {; + super.dispatchMessage(msg); + } else { + postRunnables.add((TaskRunnable) msg.getCallback()); + } + } else { + removeTask(((TaskRunnable) msg.getCallback()).task); + super.dispatchMessage(msg); + } + } + + void pause() { + isRunning = false; + } + void resume() { + if (isRunning == true) return; + isRunning = true; + for (Runnable m : postRunnables) { + m.run(); + } + postRunnables.clear(); + } + + void removeTask(Task task) { + //удаляем таску если она есть в паузе + Iterator iterator= postRunnables.iterator(); + while (iterator.hasNext()){ + TaskRunnable taskRunnable=iterator.next(); + if(taskRunnable.task==task){ + iterator.remove(); + Log.d("MyHandler","task removed:"+taskRunnable); + } + } + //удаляем таску если она была отправленна с задержкой + delayedHandler.removeTask(task); + + } + + void removeTasks() { + removeCallbacksAndMessages(null); + postRunnables.clear(); + delayedHandler.removeTasks(); + } + + void postDelayedTask(Task task,long millis){ + if(millis==0){ + TaskRunnable taskRunnable=new TaskRunnable(task); + super.post(taskRunnable); + }else{ + delayedHandler.postTaskRunnble(new TaskRunnable(task){ + @Override + public void run() { + //super.run(); + postDelayedTask(task,0); + } + },millis); + } + + } + } + + private static class DelayedHandler extends Handler{ + private List taskRunnables; + + public DelayedHandler(Looper looper) { + super(looper); + taskRunnables =new ArrayList<>(); + } + + @Override + public void dispatchMessage(Message msg) { + //убираем выполненую таску из массива тасок + if(msg.getCallback()!=null){ + TaskRunnable taskRunnable= (TaskRunnable) msg.getCallback(); + Iterator iterator= taskRunnables.iterator(); + while (iterator.hasNext()){ + TaskRunnable t=iterator.next(); + if(taskRunnable==t){ + iterator.remove(); + break; + } + } + } + super.dispatchMessage(msg); + } + + void removeTask(Task task) { + Iterator iterator= taskRunnables.iterator(); + while (iterator.hasNext()){ + TaskRunnable taskRunnable=iterator.next(); + if(taskRunnable.task==task){ + iterator.remove(); + removeCallbacks(taskRunnable); + Log.d("DelayedHandler","task removed:"+taskRunnable); + } + } + } + + void removeTasks() { + taskRunnables.clear(); + removeCallbacksAndMessages(null); + } + + public void postTaskRunnble(TaskRunnable taskRunnable,long delay){ + taskRunnables.add(taskRunnable); + super.postDelayed(taskRunnable,delay); + } } + + private static class TaskRunnable implements Runnable { + Task task; + + TaskRunnable(Task task) { + this.task = task; + } + + @Override + public void run() { + task.run(); + } + + } + } 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..5fc4595 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,7 @@ public interface CollageLoader { void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy); + + + } 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..fa8e9e4 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/CollageStrategy.java @@ -5,6 +5,6 @@ import java.util.List; public interface CollageStrategy { - + //занимается склейкой коллажа Bitmap create(List bitmaps); } diff --git a/app/src/main/java/ru/yandex/yamblz/loader/DefaultCollageStrategy.java b/app/src/main/java/ru/yandex/yamblz/loader/DefaultCollageStrategy.java new file mode 100644 index 0000000..2869931 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/loader/DefaultCollageStrategy.java @@ -0,0 +1,36 @@ +package ru.yandex.yamblz.loader; + + +import android.graphics.Bitmap; +import android.graphics.Canvas; + +import java.util.List; + +public class DefaultCollageStrategy implements CollageStrategy { + @Override + public Bitmap create(List bitmaps) { + //lenght in small bitmaps + int number=(int)Math.sqrt(bitmaps.size()); + //подразумевается что все битмапы имеют одинаковый размер + int width=bitmaps.get(0).getWidth()/number; + int height=bitmaps.get(0).getHeight()/number; + int bitmapWidth=width*number; + int bitmapHeight=height*number; + float widthScale=(float)width/bitmapWidth; + float heightScale=(float)height/bitmapHeight; + Bitmap.Config conf = Bitmap.Config.RGB_565; // see other conf types + //mutable bitmap + //новый битмап может быть чуть меньше одной обложки,тк размер не всегда делится нацело + Bitmap bmp = Bitmap.createBitmap(width*number, height*number, conf); + Canvas canvas = new Canvas(bmp); + canvas.scale(widthScale,heightScale); + for(int y=0;y imageView; + + public DefaultImageTarget(ImageView imageView) { + this.imageView = new WeakReference<>(imageView); + } + + @Override + public void onLoadBitmap(Bitmap bitmap) { + if(imageView.get()!=null){ + if(imageView.get().getTag()!=null){ + Task prevTask= (Task) imageView.get().getTag(); + CriticalSectionsManager.getHandler().removeLowPriorityTask(prevTask); + } + Task task = () -> { + if (imageView.get() != null) { + imageView.get().setImageBitmap(bitmap); + } + }; + imageView.get().setTag(task); + CriticalSectionsManager.getHandler().postLowPriorityTask(task); + } + } + +} 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..acc94ab 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/ImageTarget.java @@ -5,4 +5,7 @@ public interface ImageTarget { void onLoadBitmap(Bitmap bitmap); + + + } 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..eef882e 100644 --- a/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java +++ b/app/src/main/java/ru/yandex/yamblz/loader/StubCollageLoader.java @@ -1,31 +1,184 @@ package ru.yandex.yamblz.loader; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; import android.widget.ImageView; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; -public class StubCollageLoader implements CollageLoader { +class StubCollageLoader implements CollageLoader { + //для одного ImageTarget возможна только одна задача на загрузку битмапов + private Map callbacksMap; + private Executor collageExecutor; + StubCollageLoader() { + callbacksMap = new HashMap<>(); + collageExecutor=new ScheduledThreadPoolExecutor(5); + } + //загрузить колаж из списка и поставить его в ImageView + //взять defaultCollageStrategy @Override public void loadCollage(List urls, ImageView imageView) { - + loadCollage(urls, new DefaultImageTarget(imageView)); } + //загрузить @Override public void loadCollage(List urls, ImageTarget imageTarget) { - + loadCollage(urls, imageTarget,new DefaultCollageStrategy()); } @Override public void loadCollage(List urls, ImageView imageView, CollageStrategy collageStrategy) { - + loadCollage(urls, new DefaultImageTarget(imageView),collageStrategy); } @Override public void loadCollage(List urls, ImageTarget imageTarget, CollageStrategy collageStrategy) { + if(callbacksMap.containsKey(imageTarget)){ + LoadBitmapsCallback callback=callbacksMap.get(imageTarget); + callback.cancel(); + callbacksMap.remove(imageTarget); + } + LoadBitmapsCallback loadBitmapsCallback = new LoadBitmapsCallback(value -> { + callbacksMap.remove(imageTarget); + CreateCollageTask execute = new CreateCollageTask(value, imageTarget, collageStrategy); + execute.executeOnExecutor(collageExecutor); + }); + callbacksMap.put(imageTarget,loadBitmapsCallback); + loadBitmapsCallback.load(urls); + + } + + private class LoadBitmapsCallback { + List asyncTasks; + List bitmaps; + private int tasksDone; + private Consumer> consumer; + + LoadBitmapsCallback(Consumer> consumer) { + this.consumer = consumer; + asyncTasks = new ArrayList<>(); + bitmaps = new ArrayList<>(); + } + + void load(List urls) { + tasksDone = 0; + bitmaps.clear(); + asyncTasks.clear(); + for (String url : urls) { + LoadBitmapTask loadBitmapTask = new LoadBitmapTask(); + asyncTasks.add(loadBitmapTask); + loadBitmapTask.execute(url); + } + } + + void loadComplete(List bitmaps) { + consumer.call(bitmaps); + } + + void taskDone(Bitmap bitmap) { + bitmaps.add(bitmap); + tasksDone++; + if (tasksDone == asyncTasks.size()) { + asyncTasks.clear(); + loadComplete(bitmaps); + tasksDone = 0; + } + } + + void cancel() { + for (LoadBitmapTask loadBitmapTask : asyncTasks) { + loadBitmapTask.cancel(true); + } + } + private class LoadBitmapTask extends AsyncTask { + private InputStream input; + @Override + protected Bitmap doInBackground(String... params) { + Bitmap b = getBitmapFromURL(params[0]); + return b; + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + taskDone(bitmap); + } + + private Bitmap getBitmapFromURL(String src) { + try { + // Log.d("LoadBitmapTask",Thread.currentThread().toString()); + URL url = new URL(src); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.connect(); + input = connection.getInputStream(); + Bitmap myBitmap = BitmapFactory.decodeStream(input); + return myBitmap; + } catch (IOException e) { + // Log exception + return null; + } + finally { + if(input!=null) try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Override + protected void onCancelled() { + if(input!=null) try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + super.onCancelled(); + } + } + } + + private interface Consumer { + void call(T value); + } + + private static class CreateCollageTask extends AsyncTask{ + private List bitmaps; + private ImageTarget imageTarget; + private CollageStrategy collageStrategy; + + public CreateCollageTask(List bitmaps,ImageTarget imageTarget,CollageStrategy collageStrategy) { + this.bitmaps = bitmaps; + this.imageTarget = imageTarget; + this.collageStrategy = collageStrategy; + } + + @Override + protected Bitmap doInBackground(Void... params) { + return collageStrategy.create(bitmaps); + } + + + @Override + protected void onPostExecute(Bitmap bitmap) { + imageTarget.onLoadBitmap(bitmap); + } } + } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/activities/MainActivity.java b/app/src/main/java/ru/yandex/yamblz/ui/activities/MainActivity.java index 3b9efea..8f8cd61 100644 --- a/app/src/main/java/ru/yandex/yamblz/ui/activities/MainActivity.java +++ b/app/src/main/java/ru/yandex/yamblz/ui/activities/MainActivity.java @@ -9,8 +9,10 @@ import ru.yandex.yamblz.App; import ru.yandex.yamblz.R; +import ru.yandex.yamblz.artists.utils.DataSingleton; import ru.yandex.yamblz.developer_settings.DeveloperSettingsModule; import ru.yandex.yamblz.ui.fragments.ContentFragment; +import ru.yandex.yamblz.ui.fragments.LoadingFragment; import ru.yandex.yamblz.ui.other.ViewModifier; public class MainActivity extends BaseActivity { @@ -27,10 +29,18 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setContentView(viewModifier.modify(getLayoutInflater().inflate(R.layout.activity_main, null))); if (savedInstanceState == null) { - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.main_frame_layout, new ContentFragment()) - .commit(); + if(DataSingleton.get().hasData()){ + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_frame_layout, new ContentFragment()) + .commit(); + }else{ + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.main_frame_layout, new LoadingFragment()) + .commit(); + } + } } } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresAdapter.java b/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresAdapter.java new file mode 100644 index 0000000..12f1196 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/adapters/GenresAdapter.java @@ -0,0 +1,60 @@ +package ru.yandex.yamblz.ui.adapters; + + +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +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.List; + +import ru.yandex.yamblz.R; +import ru.yandex.yamblz.artists.utils.DataSingleton; +import ru.yandex.yamblz.loader.CollageLoaderManager; +import ru.yandex.yamblz.loader.DefaultImageTarget; +import ru.yandex.yamblz.loader.ImageTarget; + +public class GenresAdapter extends RecyclerView.Adapter { + private List genres; + + public GenresAdapter(List genres) { + this.genres = genres; + } + + @Override + public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.genre_layout,parent,false)); + } + + @Override + public void onBindViewHolder(MyViewHolder holder, int position) { + holder.bind(genres.get(position)); + } + + @Override + public int getItemCount() { + return genres.size(); + } + + static class MyViewHolder extends RecyclerView.ViewHolder { + ImageView collage; + TextView textView; + ImageTarget imageTarget; + MyViewHolder(View itemView) { + super(itemView); + collage= (ImageView) itemView.findViewById(R.id.image_collage); + textView= (TextView) itemView.findViewById(R.id.title); + imageTarget=new DefaultImageTarget(collage); + } + + void bind(String genre){ + collage.setImageDrawable(new ColorDrawable(Color.BLACK)); + textView.setText(genre); + CollageLoaderManager.getLoader().loadCollage(DataSingleton.get().getImagesForGenre(genre),imageTarget); + } + } +} 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..2d7e383 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,49 @@ 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.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import butterknife.BindView; import ru.yandex.yamblz.R; +import ru.yandex.yamblz.artists.utils.DataSingleton; +import ru.yandex.yamblz.handler.CriticalSectionsManager; +import ru.yandex.yamblz.handler.Task; +import ru.yandex.yamblz.ui.adapters.GenresAdapter; public class ContentFragment extends BaseFragment { @NonNull + @BindView(R.id.rv_genres) RecyclerView rv; @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); + GenresAdapter genresAdapter=new GenresAdapter(DataSingleton.get().getGenres()); + Task task = () -> Log.d("Tag", "delayed"); + CriticalSectionsManager.getHandler().postLowPriorityTaskDelayed(task,10000); + rv.setLayoutManager(new LinearLayoutManager(getContext(),LinearLayoutManager.VERTICAL,false)); + rv.setAdapter(genresAdapter); + rv.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if(newState== RecyclerView.SCROLL_STATE_IDLE){ + Log.d("Rv","idle"); + CriticalSectionsManager.getHandler().removeLowPriorityTask(task); + CriticalSectionsManager.getHandler().stopSection(1); + }else if(newState==RecyclerView.SCROLL_STATE_DRAGGING){ + Log.d("Rv","drag"); + CriticalSectionsManager.getHandler().startSection(1); + } + } + }); + } } diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/InternetErrorFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/InternetErrorFragment.java new file mode 100644 index 0000000..21fcfad --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/InternetErrorFragment.java @@ -0,0 +1,22 @@ +package ru.yandex.yamblz.ui.fragments; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import ru.yandex.yamblz.R; + +//при отсутствие интернета, показываем этот фрагмент +public class InternetErrorFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.internet_error_fragment, container, false); + view.findViewById(R.id.reconnect_button).setOnClickListener(v -> + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.main_frame_layout, new LoadingFragment()).commit()); + return view; + } +} diff --git a/app/src/main/java/ru/yandex/yamblz/ui/fragments/LoadingFragment.java b/app/src/main/java/ru/yandex/yamblz/ui/fragments/LoadingFragment.java new file mode 100644 index 0000000..7298eb7 --- /dev/null +++ b/app/src/main/java/ru/yandex/yamblz/ui/fragments/LoadingFragment.java @@ -0,0 +1,74 @@ +package ru.yandex.yamblz.ui.fragments; + + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.util.Log; + +import ru.yandex.yamblz.R; +import ru.yandex.yamblz.artists.DataLoadingFragment; +import ru.yandex.yamblz.artists.DataLoadingModel; +import ru.yandex.yamblz.artists.DefaultLoadingObserver; + +import static android.content.ContentValues.TAG; + +public class LoadingFragment extends Fragment implements DataLoadingModel.Observer { + + private DataLoadingModel dataLoadingModel; + private DefaultLoadingObserver defaultLoadingObserver; + + public void load() { + Log.d(TAG, "loading"); + String TAG_DATA_LOADING = "TAG_DATA_LOADING"; + //получаем фрагмент для загрузки данных(если он уже был создан) + final DataLoadingFragment retainDataLoadingFragment = + (DataLoadingFragment) getActivity().getSupportFragmentManager().findFragmentByTag(TAG_DATA_LOADING); + if (retainDataLoadingFragment != null) { + Log.d(TAG, "has data model"); + dataLoadingModel = retainDataLoadingFragment.getDataLoadingModel(); + } else { + Log.d(TAG, "no data model"); + final DataLoadingFragment dataLoadingFragment = new DataLoadingFragment(); + getActivity().getSupportFragmentManager().beginTransaction() + .add(dataLoadingFragment, TAG_DATA_LOADING) + .commit(); + dataLoadingModel = dataLoadingFragment.getDataLoadingModel(); + } + defaultLoadingObserver = new DefaultLoadingObserver(getContext()); + dataLoadingModel.registerObserver(defaultLoadingObserver); + dataLoadingModel.registerObserver(this); + dataLoadingModel.loadData(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + dataLoadingModel.unregisterObserver(defaultLoadingObserver); + dataLoadingModel.unregisterObserver(this); + defaultLoadingObserver.dispose(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + load(); + } + + @Override + public void onLoadingStart(DataLoadingModel signInModel) { + + } + + @Override + public void onLoadingSucceeded(DataLoadingModel signInModel) { + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.main_frame_layout,new ContentFragment()).commit(); + } + + @Override + public void onLoadingFailed(DataLoadingModel signInModel) { + getActivity().getSupportFragmentManager().beginTransaction(). + replace(R.id.main_frame_layout,new InternetErrorFragment()).commit(); + } +} diff --git a/app/src/main/res/layout/fragment_content.xml b/app/src/main/res/layout/fragment_content.xml index 81016ea..56ed5b3 100644 --- a/app/src/main/res/layout/fragment_content.xml +++ b/app/src/main/res/layout/fragment_content.xml @@ -1,14 +1,7 @@ - - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/genre_layout.xml b/app/src/main/res/layout/genre_layout.xml new file mode 100644 index 0000000..64424fd --- /dev/null +++ b/app/src/main/res/layout/genre_layout.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/internet_error_fragment.xml b/app/src/main/res/layout/internet_error_fragment.xml new file mode 100644 index 0000000..476f4e7 --- /dev/null +++ b/app/src/main/res/layout/internet_error_fragment.xml @@ -0,0 +1,27 @@ + + + + + + +