Audinaut-subsonic-app-android/app/src/main/java/net/nullsum/audinaut/view/UpdateView.java

287 lines
8.2 KiB
Java

/*
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 net.nullsum.audinaut.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.recyclerview.widget.RecyclerView;
import net.nullsum.audinaut.R;
import net.nullsum.audinaut.domain.MusicDirectory;
import net.nullsum.audinaut.util.DrawableTint;
import net.nullsum.audinaut.util.SilentBackgroundTask;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
public abstract class UpdateView<T> extends LinearLayout {
private static final String TAG = UpdateView.class.getSimpleName();
private static final WeakHashMap<UpdateView, ?> INSTANCES = new WeakHashMap<>();
static Handler backgroundHandler;
static Handler uiHandler;
private static Runnable updateRunnable;
private static int activeActivities = 0;
Context context;
T item;
ImageView moreButton;
View coverArtView;
boolean exists = false;
boolean pinned = false;
SilentBackgroundTask<Void> imageTask = null;
boolean checkable;
private boolean shaded = false;
private Drawable startBackgroundDrawable;
UpdateView(Context context, boolean autoUpdate) {
super(context);
this.context = context;
setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
if (autoUpdate) {
INSTANCES.put(this, null);
}
startUpdater();
}
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 = UpdateView::updateAll;
new Thread(() -> {
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<>();
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 = () -> {
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(() -> {
try {
for (UpdateView view : views) {
view.updateBackground();
}
uiHandler.post(runnable);
} catch (Throwable x) {
Log.w(TAG, "Error when updating song views.", x);
}
});
}
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;
}
@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();
}
protected abstract void setObjectImpl(T obj);
void updateBackground() {
}
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 (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.setBackground(startBackgroundDrawable);
startBackgroundDrawable = null;
}
}
void onUpdateImageView() {
}
public static class UpdateViewHolder<T> extends RecyclerView.ViewHolder {
private final View view;
private UpdateView updateView;
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) {
super(view);
this.view = view;
}
public UpdateView<T> getUpdateView() {
return updateView;
}
public View getView() {
return view;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
}