/* This file is part of Subsonic. Subsonic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Subsonic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Subsonic. If not, see <http://www.gnu.org/licenses/>. Copyright 2009 (C) Sindre Mehus */ package github.daneren2005.dsub.view; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RatingBar; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.R; import github.daneren2005.dsub.util.DrawableTint; import github.daneren2005.dsub.util.SilentBackgroundTask; public abstract class UpdateView<T> extends LinearLayout { private static final String TAG = UpdateView.class.getSimpleName(); private static final WeakHashMap<UpdateView, ?> INSTANCES = new WeakHashMap<UpdateView, Object>(); protected static Handler backgroundHandler; protected static Handler uiHandler; private static Runnable updateRunnable; private static int activeActivities = 0; protected Context context; protected T item; protected RatingBar ratingBar; protected ImageButton starButton; protected ImageView moreButton; protected View coverArtView; protected boolean exists = false; protected boolean pinned = false; protected boolean shaded = false; protected boolean starred = false; protected boolean isStarred = false; protected int isRated = 0; protected int rating = 0; protected SilentBackgroundTask<Void> imageTask = null; protected Drawable startBackgroundDrawable; protected final boolean autoUpdate; protected boolean checkable; public UpdateView(Context context) { this(context, true); } public UpdateView(Context context, boolean autoUpdate) { super(context); this.context = context; this.autoUpdate = autoUpdate; setLayoutParams(new AbsListView.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); if(autoUpdate) { INSTANCES.put(this, null); } startUpdater(); } @Override public void setPressed(boolean pressed) { } public void setObject(T obj) { if(item == obj) { return; } item = obj; if(imageTask != null) { imageTask.cancel(); imageTask = null; } if(coverArtView != null && coverArtView instanceof ImageView) { ((ImageView) coverArtView).setImageDrawable(null); } setObjectImpl(obj); updateBackground(); update(); } public void setObject(T obj1, Object obj2) { setObject(obj1, null); } protected abstract void setObjectImpl(T obj); private static synchronized void startUpdater() { if(uiHandler != null) { return; } uiHandler = new Handler(); // Needed so handler is never null until thread creates it backgroundHandler = uiHandler; updateRunnable = new Runnable() { @Override public void run() { updateAll(); } }; new Thread(new Runnable() { public void run() { Looper.prepare(); backgroundHandler = new Handler(Looper.myLooper()); uiHandler.post(updateRunnable); Looper.loop(); } }, "UpdateView").start(); } public static synchronized void triggerUpdate() { if(backgroundHandler != null) { uiHandler.removeCallbacksAndMessages(null); backgroundHandler.removeCallbacksAndMessages(null); uiHandler.post(updateRunnable); } } private static void updateAll() { try { // If nothing can see this, stop updating if(activeActivities == 0) { activeActivities--; return; } List<UpdateView> views = new ArrayList<UpdateView>(); for (UpdateView view : INSTANCES.keySet()) { if (view.isShown()) { views.add(view); } } if(views.size() > 0) { updateAllLive(views); } else { uiHandler.postDelayed(updateRunnable, 2000L); } } catch (Throwable x) { Log.w(TAG, "Error when updating song views.", x); } } private static void updateAllLive(final List<UpdateView> views) { final Runnable runnable = new Runnable() { @Override public void run() { try { for(UpdateView view: views) { view.update(); } } catch (Throwable x) { Log.w(TAG, "Error when updating song views.", x); } uiHandler.postDelayed(updateRunnable, 1000L); } }; backgroundHandler.post(new Runnable() { @Override public void run() { try { for(UpdateView view: views) { view.updateBackground(); } uiHandler.post(runnable); } catch (Throwable x) { Log.w(TAG, "Error when updating song views.", x); } } }); } public static boolean hasActiveActivity() { return activeActivities > 0; } public static void addActiveActivity() { activeActivities++; if(activeActivities == 0 && uiHandler != null && updateRunnable != null) { activeActivities++; uiHandler.post(updateRunnable); } } public static void removeActiveActivity() { activeActivities--; } public static MusicDirectory.Entry findEntry(MusicDirectory.Entry entry) { for(UpdateView view: INSTANCES.keySet()) { MusicDirectory.Entry check = null; if(view instanceof SongView) { check = ((SongView) view).getEntry(); } else if(view instanceof AlbumView) { check = ((AlbumView) view).getEntry(); } if(check != null && entry != check && check.getId().equals(entry.getId())) { return check; } } return null; } protected void updateBackground() { } protected void update() { if(moreButton != null) { if(exists || pinned) { if(!shaded) { moreButton.setImageResource(exists ? R.drawable.download_cached : R.drawable.download_pinned); shaded = true; } } else { if(shaded) { moreButton.setImageResource(DrawableTint.getDrawableRes(context, R.attr.download_none)); shaded = false; } } } if(starButton != null) { if(isStarred) { if(!starred) { if(starButton.getDrawable() == null) { starButton.setImageDrawable(DrawableTint.getTintedDrawable(context, R.drawable.ic_toggle_star)); } starButton.setVisibility(View.VISIBLE); starred = true; } } else { if(starred) { starButton.setVisibility(View.GONE); starred = false; } } } if(ratingBar != null && isRated != rating) { if(isRated > 0 && rating == 0) { ratingBar.setVisibility(View.VISIBLE); } else if(isRated == 0 && rating > 0) { ratingBar.setVisibility(View.GONE); } ratingBar.setRating(isRated); rating = isRated; } if(coverArtView != null && coverArtView instanceof RecyclingImageView) { RecyclingImageView recyclingImageView = (RecyclingImageView) coverArtView; if(recyclingImageView.isInvalidated()) { onUpdateImageView(); } } } public boolean isCheckable() { return checkable; } public void setChecked(boolean checked) { View child = getChildAt(0); if (checked && startBackgroundDrawable == null) { startBackgroundDrawable = child.getBackground(); child.setBackgroundColor(DrawableTint.getColorRes(context, R.attr.colorPrimary)); } else if (!checked && startBackgroundDrawable != null) { child.setBackgroundDrawable(startBackgroundDrawable); startBackgroundDrawable = null; } } public void onClick() { } public void onUpdateImageView() { } public static class UpdateViewHolder<T> extends RecyclerView.ViewHolder { private UpdateView updateView; private View view; private T item; public UpdateViewHolder(UpdateView itemView) { super(itemView); this.updateView = itemView; this.view = itemView; } // Different is so that call is not ambiguous public UpdateViewHolder(View view, boolean different) { super(view); this.view = view; } public UpdateView<T> getUpdateView() { return updateView; } public View getView() { return view; } public void setItem(T item) { this.item = item; } public T getItem() { return item; } } }