mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-03-05 20:07:50 +01:00
Merge pull request #382 from nitehu/refactor/menudrawer_to_navigationui
Refactor menudrawer to navigationui
This commit is contained in:
commit
b235acf521
@ -1,13 +0,0 @@
|
||||
apply from: bootstrap.androidModule
|
||||
|
||||
android {
|
||||
lintOptions {
|
||||
baselineFile file("lint-baseline.xml")
|
||||
abortOnError true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.appcompat:appcompat-resources:1.2.0"
|
||||
implementation other.timber
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="4" by="lint 2.3.3">
|
||||
|
||||
<issue
|
||||
id="InlinedApi"
|
||||
message="Field requires API level 17 (current min is 14): `android.view.View#LAYOUT_DIRECTION_RTL`"
|
||||
errorLine1=" if (mSlideDrawable != null) mSlideDrawable.setIsRtl(layoutDirection == LAYOUT_DIRECTION_RTL);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/MenuDrawer.java"
|
||||
line="882"
|
||||
column="80"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="InlinedApi"
|
||||
message="Field requires API level 17 (current min is 14): `android.view.View#LAYOUT_DIRECTION_RTL`"
|
||||
errorLine1=" mSlideDrawable.setIsRtl(ViewHelper.getLayoutDirection(this) == LAYOUT_DIRECTION_RTL);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/MenuDrawer.java"
|
||||
line="1325"
|
||||
column="72"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="OldTargetApi"
|
||||
message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the `android.os.Build.VERSION_CODES` javadoc for details."
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="41"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `minSdkVersion` value (`7`) is not used; it is always overridden by the value specified in the Gradle build script (`14`)"
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="15"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="GradleOverrides"
|
||||
message="This `targetSdkVersion` value (`16`) is not used; it is always overridden by the value specified in the Gradle build script (`22`)"
|
||||
errorLine1=" <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/AndroidManifest.xml"
|
||||
line="7"
|
||||
column="41"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ParcelClassLoader"
|
||||
message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead."
|
||||
errorLine1=" mState = in.readBundle();"
|
||||
errorLine2=" ~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/MenuDrawer.java"
|
||||
line="1630"
|
||||
column="25"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="41"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="43"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="51"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="53"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="59"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="61"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="67"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="69"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 14"
|
||||
errorLine1=" if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="77"
|
||||
column="28"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/compat/ActionBarHelper.java"
|
||||
line="79"
|
||||
column="20"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="572"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="580"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="588"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is always >= 14"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/DraggableDrawer.java"
|
||||
line="596"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="FloatMath"
|
||||
message="Use `java.lang.Math#sqrt` instead of `android.util.FloatMath#sqrt()` since it is faster as of API 8"
|
||||
errorLine1=" float hyp = FloatMath.sqrt(dx * dx + dy * dy);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/Scroller.java"
|
||||
line="374"
|
||||
column="25"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="FloatMath"
|
||||
message="Use `java.lang.Math#sqrt` instead of `android.util.FloatMath#sqrt()` since it is faster as of API 8"
|
||||
errorLine1=" float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);"
|
||||
errorLine2=" ~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/net/simonvt/menudrawer/Scroller.java"
|
||||
line="391"
|
||||
column="26"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.simonvt.menudrawer"
|
||||
android:versionCode="6"
|
||||
android:versionName="3.0.2">
|
||||
</manifest>
|
@ -1,99 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* FrameLayout which caches the hardware layer if available.
|
||||
* <p/>
|
||||
* If it's not posted twice the layer either wont be built on start, or it'll be built twice.
|
||||
*/
|
||||
class BuildLayerFrameLayout extends FrameLayout {
|
||||
|
||||
private boolean mChanged;
|
||||
|
||||
private boolean mHardwareLayersEnabled = true;
|
||||
|
||||
private boolean mAttached;
|
||||
|
||||
private boolean mFirst = true;
|
||||
|
||||
public BuildLayerFrameLayout(Context context) {
|
||||
super(context);
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
public BuildLayerFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
void setHardwareLayersEnabled(boolean enabled) {
|
||||
mHardwareLayersEnabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mAttached = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mAttached = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
if (MenuDrawer.USE_TRANSLATIONS && mHardwareLayersEnabled) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mChanged = true;
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
|
||||
if (mChanged && MenuDrawer.USE_TRANSLATIONS) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mAttached) {
|
||||
final int layerType = getLayerType();
|
||||
// If it's already a hardware layer, it'll be built anyway.
|
||||
if (layerType != LAYER_TYPE_HARDWARE || mFirst) {
|
||||
mFirst = false;
|
||||
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||
buildLayer();
|
||||
setLayerType(LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mChanged = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* A specialized Drawable that fills the Canvas with a specified color.
|
||||
* Note that a ColorDrawable ignores the ColorFilter.
|
||||
* <p/>
|
||||
* <p>It can be defined in an XML file with the <code><color></code> element.</p>
|
||||
*
|
||||
* @attr ref android.R.styleable#ColorDrawable_color
|
||||
*/
|
||||
class ColorDrawable extends Drawable {
|
||||
|
||||
private ColorState mState;
|
||||
private final Paint mPaint = new Paint();
|
||||
|
||||
/** Creates a new black ColorDrawable. */
|
||||
public ColorDrawable() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ColorDrawable with the specified color.
|
||||
*
|
||||
* @param color The color to draw.
|
||||
*/
|
||||
public ColorDrawable(int color) {
|
||||
this(null);
|
||||
setColor(color);
|
||||
}
|
||||
|
||||
private ColorDrawable(ColorState state) {
|
||||
mState = new ColorState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return super.getChangingConfigurations() | mState.mChangingConfigurations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if ((mState.mUseColor >>> 24) != 0) {
|
||||
mPaint.setColor(mState.mUseColor);
|
||||
canvas.drawRect(getBounds(), mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the drawable's color value.
|
||||
*
|
||||
* @return int The color to draw.
|
||||
*/
|
||||
public int getColor() {
|
||||
return mState.mUseColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the drawable's color value. This action will clobber the results of prior calls to
|
||||
* {@link #setAlpha(int)} on this object, which side-affected the underlying color.
|
||||
*
|
||||
* @param color The color to draw.
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
if (mState.mBaseColor != color || mState.mUseColor != color) {
|
||||
invalidateSelf();
|
||||
mState.mBaseColor = mState.mUseColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alpha value of this drawable's color.
|
||||
*
|
||||
* @return A value between 0 and 255.
|
||||
*/
|
||||
public int getAlpha() {
|
||||
return mState.mUseColor >>> 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color's alpha value.
|
||||
*
|
||||
* @param alpha The alpha value to set, between 0 and 255.
|
||||
*/
|
||||
public void setAlpha(int alpha) {
|
||||
alpha += alpha >> 7; // make it 0..256
|
||||
int baseAlpha = mState.mBaseColor >>> 24;
|
||||
int useAlpha = baseAlpha * alpha >> 8;
|
||||
int oldUseColor = mState.mUseColor;
|
||||
mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
|
||||
if (oldUseColor != mState.mUseColor) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting a color filter on a ColorDrawable has no effect.
|
||||
*
|
||||
* @param colorFilter Ignore.
|
||||
*/
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
}
|
||||
|
||||
public int getOpacity() {
|
||||
switch (mState.mUseColor >>> 24) {
|
||||
case 255:
|
||||
return PixelFormat.OPAQUE;
|
||||
case 0:
|
||||
return PixelFormat.TRANSPARENT;
|
||||
}
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
mState.mChangingConfigurations = getChangingConfigurations();
|
||||
return mState;
|
||||
}
|
||||
|
||||
static final class ColorState extends ConstantState {
|
||||
|
||||
int mBaseColor; // base color, independent of setAlpha()
|
||||
int mUseColor; // basecolor modulated by setAlpha()
|
||||
int mChangingConfigurations;
|
||||
|
||||
ColorState(ColorState state) {
|
||||
if (state != null) {
|
||||
mBaseColor = state.mBaseColor;
|
||||
mUseColor = state.mUseColor;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new ColorDrawable(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable(Resources res) {
|
||||
return new ColorDrawable(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return mChangingConfigurations;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,619 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
public abstract class DraggableDrawer extends MenuDrawer {
|
||||
|
||||
/**
|
||||
* Key used when saving menu visibility state.
|
||||
*/
|
||||
private static final String STATE_MENU_VISIBLE = "net.simonvt.menudrawer.MenuDrawer.menuVisible";
|
||||
|
||||
/**
|
||||
* Interpolator used for peeking at the drawer.
|
||||
*/
|
||||
private static final Interpolator PEEK_INTERPOLATOR = new PeekInterpolator();
|
||||
|
||||
/**
|
||||
* The maximum alpha of the dark menu overlay used for dimming the menu.
|
||||
*/
|
||||
protected static final int MAX_MENU_OVERLAY_ALPHA = 185;
|
||||
|
||||
/**
|
||||
* Default delay from {@link #peekDrawer()} is called until first animation is run.
|
||||
*/
|
||||
private static final long DEFAULT_PEEK_START_DELAY = 5000;
|
||||
|
||||
/**
|
||||
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
|
||||
*/
|
||||
private static final long DEFAULT_PEEK_DELAY = 10000;
|
||||
|
||||
/**
|
||||
* The duration of the peek animation.
|
||||
*/
|
||||
protected static final int PEEK_DURATION = 5000;
|
||||
|
||||
/**
|
||||
* Distance in dp from closed position from where the drawer is considered closed with regards to touch events.
|
||||
*/
|
||||
private static final int CLOSE_ENOUGH = 3;
|
||||
|
||||
protected static final int INVALID_POINTER = -1;
|
||||
|
||||
/**
|
||||
* Slop before starting a drag.
|
||||
*/
|
||||
protected int mTouchSlop;
|
||||
|
||||
/**
|
||||
* Runnable used when the peek animation is running.
|
||||
*/
|
||||
protected final Runnable mPeekRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
peekDrawerInvalidate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Runnable used when animating the drawer open/closed.
|
||||
*/
|
||||
private final Runnable mDragRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
postAnimationInvalidate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether the drawer is currently being dragged.
|
||||
*/
|
||||
protected boolean mIsDragging;
|
||||
|
||||
/**
|
||||
* The current pointer id.
|
||||
*/
|
||||
protected int mActivePointerId = INVALID_POINTER;
|
||||
|
||||
/**
|
||||
* The initial X position of a drag.
|
||||
*/
|
||||
protected float mInitialMotionX;
|
||||
|
||||
/**
|
||||
* The initial Y position of a drag.
|
||||
*/
|
||||
protected float mInitialMotionY;
|
||||
|
||||
/**
|
||||
* The last X position of a drag.
|
||||
*/
|
||||
protected float mLastMotionX = -1;
|
||||
|
||||
/**
|
||||
* The last Y position of a drag.
|
||||
*/
|
||||
protected float mLastMotionY = -1;
|
||||
|
||||
/**
|
||||
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
|
||||
*/
|
||||
protected long mPeekDelay;
|
||||
|
||||
/**
|
||||
* Scroller used for the peek drawer animation.
|
||||
*/
|
||||
protected Scroller mPeekScroller;
|
||||
|
||||
/**
|
||||
* Velocity tracker used when animating the drawer open/closed after a drag.
|
||||
*/
|
||||
protected VelocityTracker mVelocityTracker;
|
||||
|
||||
/**
|
||||
* Maximum velocity allowed when animating the drawer open/closed.
|
||||
*/
|
||||
protected int mMaxVelocity;
|
||||
|
||||
/**
|
||||
* Indicates whether the menu should be offset when dragging the drawer.
|
||||
*/
|
||||
protected boolean mOffsetMenu = true;
|
||||
|
||||
/**
|
||||
* Distance in px from closed position from where the drawer is considered closed with regards to touch events.
|
||||
*/
|
||||
protected int mCloseEnough;
|
||||
|
||||
/**
|
||||
* Runnable used for first call to {@link #startPeek()} after {@link #peekDrawer()} has been called.
|
||||
*/
|
||||
private Runnable mPeekStartRunnable;
|
||||
|
||||
/**
|
||||
* Scroller used when animating the drawer open/closed.
|
||||
*/
|
||||
private Scroller mScroller;
|
||||
|
||||
/**
|
||||
* Indicates whether the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}.
|
||||
*/
|
||||
protected boolean mLayerTypeHardware;
|
||||
|
||||
DraggableDrawer(Activity activity, int dragMode) {
|
||||
super(activity, dragMode);
|
||||
}
|
||||
|
||||
public DraggableDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public DraggableDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public DraggableDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
|
||||
final ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
|
||||
|
||||
mScroller = new Scroller(context, MenuDrawer.SMOOTH_INTERPOLATOR);
|
||||
mPeekScroller = new Scroller(context, DraggableDrawer.PEEK_INTERPOLATOR);
|
||||
|
||||
mCloseEnough = dpToPx(DraggableDrawer.CLOSE_ENOUGH);
|
||||
}
|
||||
|
||||
public void toggleMenu(boolean animate) {
|
||||
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
|
||||
closeMenu(animate);
|
||||
} else if (mDrawerState == STATE_CLOSED || mDrawerState == STATE_CLOSING) {
|
||||
openMenu(animate);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMenuVisible() {
|
||||
return mMenuVisible;
|
||||
}
|
||||
|
||||
public void setMenuSize(final int size) {
|
||||
mMenuSize = size;
|
||||
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
|
||||
setOffsetPixels(mMenuSize);
|
||||
}
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setOffsetMenuEnabled(boolean offsetMenu) {
|
||||
if (offsetMenu != mOffsetMenu) {
|
||||
mOffsetMenu = offsetMenu;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getOffsetMenuEnabled() {
|
||||
return mOffsetMenu;
|
||||
}
|
||||
|
||||
public void peekDrawer() {
|
||||
peekDrawer(DEFAULT_PEEK_START_DELAY, DEFAULT_PEEK_DELAY);
|
||||
}
|
||||
|
||||
public void peekDrawer(long delay) {
|
||||
peekDrawer(DEFAULT_PEEK_START_DELAY, delay);
|
||||
}
|
||||
|
||||
public void peekDrawer(final long startDelay, final long delay) {
|
||||
if (startDelay < 0) {
|
||||
throw new IllegalArgumentException("startDelay must be zero or larger.");
|
||||
}
|
||||
if (delay < 0) {
|
||||
throw new IllegalArgumentException("delay must be zero or larger");
|
||||
}
|
||||
|
||||
removeCallbacks(mPeekRunnable);
|
||||
removeCallbacks(mPeekStartRunnable);
|
||||
|
||||
mPeekDelay = delay;
|
||||
mPeekStartRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startPeek();
|
||||
}
|
||||
};
|
||||
postDelayed(mPeekStartRunnable, startDelay);
|
||||
}
|
||||
|
||||
public void setHardwareLayerEnabled(boolean enabled) {
|
||||
if (enabled != mHardwareLayersEnabled) {
|
||||
mHardwareLayersEnabled = enabled;
|
||||
mMenuContainer.setHardwareLayersEnabled(enabled);
|
||||
mContentContainer.setHardwareLayersEnabled(enabled);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
public int getTouchMode() {
|
||||
return mTouchMode;
|
||||
}
|
||||
|
||||
public void setTouchMode(int mode) {
|
||||
if (mTouchMode != mode) {
|
||||
mTouchMode = mode;
|
||||
updateTouchAreaSize();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTouchBezelSize(int size) {
|
||||
mTouchBezelSize = size;
|
||||
}
|
||||
|
||||
public int getTouchBezelSize() {
|
||||
return mTouchBezelSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, set the layer type to {@link android.view.View#LAYER_TYPE_HARDWARE}.
|
||||
*/
|
||||
protected void startLayerTranslation() {
|
||||
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
|
||||
mLayerTypeHardware = true;
|
||||
mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}, this will set it to
|
||||
* {@link View#LAYER_TYPE_NONE}.
|
||||
*/
|
||||
protected void stopLayerTranslation() {
|
||||
if (mLayerTypeHardware) {
|
||||
mLayerTypeHardware = false;
|
||||
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a drag has been ended.
|
||||
*/
|
||||
protected void endDrag() {
|
||||
mIsDragging = false;
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops ongoing animation of the drawer.
|
||||
*/
|
||||
protected void stopAnimation() {
|
||||
removeCallbacks(mDragRunnable);
|
||||
mScroller.abortAnimation();
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a drawer animation has successfully completed.
|
||||
*/
|
||||
private void completeAnimation() {
|
||||
mScroller.abortAnimation();
|
||||
final int finalX = mScroller.getFinalX();
|
||||
setOffsetPixels(finalX);
|
||||
setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
protected void cancelContentTouch() {
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
|
||||
final int childCount = getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
getChildAt(i).dispatchTouchEvent(cancelEvent);
|
||||
}
|
||||
mContentContainer.dispatchTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the drawer to the position passed.
|
||||
*
|
||||
* @param position The position the content is moved to.
|
||||
* @param velocity Optional velocity if called by releasing a drag event.
|
||||
* @param animate Whether the move is animated.
|
||||
*/
|
||||
protected void animateOffsetTo(int position, int velocity, boolean animate) {
|
||||
endDrag();
|
||||
endPeek();
|
||||
|
||||
final int startX = (int) mOffsetPixels;
|
||||
final int dx = position - startX;
|
||||
if (dx == 0 || !animate) {
|
||||
setOffsetPixels(position);
|
||||
setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN);
|
||||
stopLayerTranslation();
|
||||
return;
|
||||
}
|
||||
|
||||
int duration;
|
||||
|
||||
velocity = Math.abs(velocity);
|
||||
if (velocity > 0) {
|
||||
duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity));
|
||||
} else {
|
||||
duration = (int) (600.f * Math.abs((float) dx / mMenuSize));
|
||||
}
|
||||
|
||||
duration = Math.min(duration, mMaxAnimationDuration);
|
||||
animateOffsetTo(position, duration);
|
||||
}
|
||||
|
||||
protected void animateOffsetTo(int position, int duration) {
|
||||
final int startX = (int) mOffsetPixels;
|
||||
final int dx = position - startX;
|
||||
|
||||
if (dx > 0) {
|
||||
setDrawerState(STATE_OPENING);
|
||||
mScroller.startScroll(startX, 0, dx, 0, duration);
|
||||
} else {
|
||||
setDrawerState(STATE_CLOSING);
|
||||
mScroller.startScroll(startX, 0, dx, 0, duration);
|
||||
}
|
||||
|
||||
startLayerTranslation();
|
||||
|
||||
postAnimationInvalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when each frame in the drawer animation should be drawn.
|
||||
*/
|
||||
private void postAnimationInvalidate() {
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
final int oldX = (int) mOffsetPixels;
|
||||
final int x = mScroller.getCurrX();
|
||||
|
||||
if (x != oldX) setOffsetPixels(x);
|
||||
if (x != mScroller.getFinalX()) {
|
||||
postOnAnimation(mDragRunnable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
completeAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts peek drawer animation.
|
||||
*/
|
||||
protected void startPeek() {
|
||||
initPeekScroller();
|
||||
|
||||
startLayerTranslation();
|
||||
peekDrawerInvalidate();
|
||||
}
|
||||
|
||||
protected abstract void initPeekScroller();
|
||||
|
||||
/**
|
||||
* Callback when each frame in the peek drawer animation should be drawn.
|
||||
*/
|
||||
private void peekDrawerInvalidate() {
|
||||
if (mPeekScroller.computeScrollOffset()) {
|
||||
final int oldX = (int) mOffsetPixels;
|
||||
final int x = mPeekScroller.getCurrX();
|
||||
if (x != oldX) setOffsetPixels(x);
|
||||
|
||||
if (!mPeekScroller.isFinished()) {
|
||||
postOnAnimation(mPeekRunnable);
|
||||
return;
|
||||
|
||||
} else if (mPeekDelay > 0) {
|
||||
mPeekStartRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startPeek();
|
||||
}
|
||||
};
|
||||
postDelayed(mPeekStartRunnable, mPeekDelay);
|
||||
}
|
||||
}
|
||||
|
||||
completePeek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the peek drawer animation has successfully completed.
|
||||
*/
|
||||
private void completePeek() {
|
||||
mPeekScroller.abortAnimation();
|
||||
|
||||
setOffsetPixels(0);
|
||||
|
||||
setDrawerState(STATE_CLOSED);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops ongoing peek drawer animation.
|
||||
*/
|
||||
protected void endPeek() {
|
||||
removeCallbacks(mPeekStartRunnable);
|
||||
removeCallbacks(mPeekRunnable);
|
||||
stopLayerTranslation();
|
||||
}
|
||||
|
||||
protected boolean isCloseEnough() {
|
||||
return Math.abs(mOffsetPixels) <= mCloseEnough;
|
||||
}
|
||||
|
||||
protected boolean canChildrenScroll(int dx, int dy, int x, int y) {
|
||||
boolean canScroll = false;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case RIGHT:
|
||||
if (!mMenuVisible) {
|
||||
canScroll = canChildScrollHorizontally(mContentContainer, false, dx,
|
||||
x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
} else {
|
||||
canScroll = canChildScrollHorizontally(mMenuContainer, false, dx,
|
||||
x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
}
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
if (!mMenuVisible) {
|
||||
canScroll = canChildScrollVertically(mContentContainer, false, dy,
|
||||
x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
} else {
|
||||
canScroll = canChildScrollVertically(mMenuContainer, false, dy,
|
||||
x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer));
|
||||
}
|
||||
}
|
||||
|
||||
return canScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests scrollability within child views of v given a delta of dx.
|
||||
*
|
||||
* @param v View to test for horizontal scrollability
|
||||
* @param checkV Whether the view should be checked for draggability
|
||||
* @param dx Delta scrolled in pixels
|
||||
* @param x X coordinate of the active touch point
|
||||
* @param y Y coordinate of the active touch point
|
||||
* @return true if child views of v can be scrolled by delta of dx.
|
||||
*/
|
||||
protected boolean canChildScrollHorizontally(View v, boolean checkV, int dx, int x, int y) {
|
||||
if (v instanceof ViewGroup) {
|
||||
final ViewGroup group = (ViewGroup) v;
|
||||
|
||||
final int count = group.getChildCount();
|
||||
// Count backwards - let topmost views consume scroll distance first.
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
final View child = group.getChildAt(i);
|
||||
|
||||
final int childLeft = child.getLeft() + supportGetTranslationX(child);
|
||||
final int childRight = child.getRight() + supportGetTranslationX(child);
|
||||
final int childTop = child.getTop() + supportGetTranslationY(child);
|
||||
final int childBottom = child.getBottom() + supportGetTranslationY(child);
|
||||
|
||||
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
|
||||
&& canChildScrollHorizontally(child, true, dx, x - childLeft, y - childTop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests scrollability within child views of v given a delta of dx.
|
||||
*
|
||||
* @param v View to test for horizontal scrollability
|
||||
* @param checkV Whether the view should be checked for draggability
|
||||
* @param dx Delta scrolled in pixels
|
||||
* @param x X coordinate of the active touch point
|
||||
* @param y Y coordinate of the active touch point
|
||||
* @return true if child views of v can be scrolled by delta of dx.
|
||||
*/
|
||||
protected boolean canChildScrollVertically(View v, boolean checkV, int dx, int x, int y) {
|
||||
if (v instanceof ViewGroup) {
|
||||
final ViewGroup group = (ViewGroup) v;
|
||||
|
||||
final int count = group.getChildCount();
|
||||
// Count backwards - let topmost views consume scroll distance first.
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
final View child = group.getChildAt(i);
|
||||
|
||||
final int childLeft = child.getLeft() + supportGetTranslationX(child);
|
||||
final int childRight = child.getRight() + supportGetTranslationX(child);
|
||||
final int childTop = child.getTop() + supportGetTranslationY(child);
|
||||
final int childBottom = child.getBottom() + supportGetTranslationY(child);
|
||||
|
||||
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
|
||||
&& canChildScrollVertically(child, true, dx, x - childLeft, y - childTop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
|
||||
}
|
||||
|
||||
protected float getXVelocity(VelocityTracker velocityTracker) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||
return velocityTracker.getXVelocity(mActivePointerId);
|
||||
}
|
||||
|
||||
return velocityTracker.getXVelocity();
|
||||
}
|
||||
|
||||
protected float getYVelocity(VelocityTracker velocityTracker) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||
return velocityTracker.getYVelocity(mActivePointerId);
|
||||
}
|
||||
|
||||
return velocityTracker.getYVelocity();
|
||||
}
|
||||
|
||||
private int supportGetTranslationY(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return (int) v.getTranslationY();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int supportGetTranslationX(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return (int) v.getTranslationX();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void saveState(Bundle state) {
|
||||
final boolean menuVisible = mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING;
|
||||
state.putBoolean(STATE_MENU_VISIBLE, menuVisible);
|
||||
}
|
||||
|
||||
public void restoreState(Parcelable in) {
|
||||
super.restoreState(in);
|
||||
Bundle state = (Bundle) in;
|
||||
final boolean menuOpen = state.getBoolean(STATE_MENU_VISIBLE);
|
||||
if (menuOpen) {
|
||||
openMenu(false);
|
||||
} else {
|
||||
setOffsetPixels(0);
|
||||
}
|
||||
mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED;
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* This class encapsulates scrolling. The duration of the scroll
|
||||
* can be passed in the constructor and specifies the maximum time that
|
||||
* the scrolling animation should take. Past this time, the scrolling is
|
||||
* automatically moved to its final stage and computeScrollOffset()
|
||||
* will always return false to indicate that scrolling is over.
|
||||
*/
|
||||
class FloatScroller {
|
||||
|
||||
private float mStart;
|
||||
private float mFinal;
|
||||
|
||||
private float mCurr;
|
||||
private long mStartTime;
|
||||
private int mDuration;
|
||||
private float mDurationReciprocal;
|
||||
private float mDeltaX;
|
||||
private boolean mFinished;
|
||||
private Interpolator mInterpolator;
|
||||
|
||||
/**
|
||||
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||
* null, the default (viscous) interpolator will be used. Specify whether or
|
||||
* not to support progressive "flywheel" behavior in flinging.
|
||||
*/
|
||||
public FloatScroller(Interpolator interpolator) {
|
||||
mFinished = true;
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the scroller has finished scrolling.
|
||||
*
|
||||
* @return True if the scroller has finished scrolling, false otherwise.
|
||||
*/
|
||||
public final boolean isFinished() {
|
||||
return mFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the finished field to a particular value.
|
||||
*
|
||||
* @param finished The new finished value.
|
||||
*/
|
||||
public final void forceFinished(boolean finished) {
|
||||
mFinished = finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how long the scroll event will take, in milliseconds.
|
||||
*
|
||||
* @return The duration of the scroll in milliseconds.
|
||||
*/
|
||||
public final int getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current offset in the scroll.
|
||||
*
|
||||
* @return The new offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final float getCurr() {
|
||||
return mCurr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset in the scroll.
|
||||
*
|
||||
* @return The start offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final float getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final float getFinal() {
|
||||
return mFinal;
|
||||
}
|
||||
|
||||
public boolean computeScrollOffset() {
|
||||
if (mFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
|
||||
if (timePassed < mDuration) {
|
||||
float x = timePassed * mDurationReciprocal;
|
||||
x = mInterpolator.getInterpolation(x);
|
||||
mCurr = mStart + x * mDeltaX;
|
||||
|
||||
} else {
|
||||
mCurr = mFinal;
|
||||
mFinished = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void startScroll(float start, float delta, int duration) {
|
||||
mFinished = false;
|
||||
mDuration = duration;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStart = start;
|
||||
mFinal = start + delta;
|
||||
mDeltaX = delta;
|
||||
mDurationReciprocal = 1.0f / (float) mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
|
||||
* aborting the animating cause the scroller to move to the final x and y
|
||||
* position
|
||||
*
|
||||
* @see #forceFinished(boolean)
|
||||
*/
|
||||
public void abortAnimation() {
|
||||
mCurr = mFinal;
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the scroll animation. This allows a running animation to scroll
|
||||
* further and longer, when used with {@link #setFinal(float)}.
|
||||
*
|
||||
* @param extend Additional time to scroll in milliseconds.
|
||||
* @see #setFinal(float)
|
||||
*/
|
||||
public void extendDuration(int extend) {
|
||||
int passed = timePassed();
|
||||
mDuration = passed + extend;
|
||||
mDurationReciprocal = 1.0f / mDuration;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed since the beginning of the scrolling.
|
||||
*
|
||||
* @return The elapsed time in milliseconds.
|
||||
*/
|
||||
public int timePassed() {
|
||||
return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
}
|
||||
|
||||
public void setFinal(float newVal) {
|
||||
mFinal = newVal;
|
||||
mDeltaX = mFinal - mStart;
|
||||
mFinished = false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,28 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* FrameLayout which doesn't let touch events propagate to views positioned behind it in the view hierarchy.
|
||||
*/
|
||||
class NoClickThroughFrameLayout extends BuildLayerFrameLayout {
|
||||
|
||||
public NoClickThroughFrameLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NoClickThroughFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,778 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
|
||||
public class OverlayDrawer extends DraggableDrawer {
|
||||
private int mPeekSize;
|
||||
|
||||
private Runnable mRevealRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cancelContentTouch();
|
||||
int animateTo = 0;
|
||||
switch (getPosition()) {
|
||||
case RIGHT:
|
||||
case BOTTOM:
|
||||
animateTo = -mPeekSize;
|
||||
break;
|
||||
|
||||
default:
|
||||
animateTo = mPeekSize;
|
||||
break;
|
||||
}
|
||||
animateOffsetTo(animateTo, 250);
|
||||
}
|
||||
};
|
||||
|
||||
OverlayDrawer(Activity activity, int dragMode) {
|
||||
super(activity, dragMode);
|
||||
}
|
||||
|
||||
public OverlayDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public OverlayDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public OverlayDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
if (USE_TRANSLATIONS) {
|
||||
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
mContentContainer.setHardwareLayersEnabled(false);
|
||||
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
mPeekSize = dpToPx(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(Canvas canvas) {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuOverlay.setBounds(offsetPixels, 0, width, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuOverlay.setBounds(0, 0, width + offsetPixels, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuOverlay.setBounds(0, offsetPixels, width, height);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuOverlay.setBounds(0, 0, width, height + offsetPixels);
|
||||
break;
|
||||
}
|
||||
|
||||
mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * openRatio));
|
||||
mMenuOverlay.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu(boolean animate) {
|
||||
int animateTo = 0;
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case TOP:
|
||||
animateTo = mMenuSize;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
case BOTTOM:
|
||||
animateTo = -mMenuSize;
|
||||
break;
|
||||
}
|
||||
|
||||
animateOffsetTo(animateTo, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeMenu(boolean animate) {
|
||||
animateOffsetTo(0, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOffsetPixelsChanged(int offsetPixels) {
|
||||
if (USE_TRANSLATIONS) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.setTranslationX(offsetPixels - mMenuSize);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.setTranslationY(offsetPixels - mMenuSize);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.setTranslationX(offsetPixels + mMenuSize);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.setTranslationY(offsetPixels + mMenuSize);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
mMenuContainer.offsetTopAndBottom(offsetPixels - mMenuContainer.getBottom());
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.offsetTopAndBottom(offsetPixels - (mMenuContainer.getTop() - getHeight()));
|
||||
break;
|
||||
|
||||
case LEFT:
|
||||
mMenuContainer.offsetLeftAndRight(offsetPixels - mMenuContainer.getRight());
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.offsetLeftAndRight(offsetPixels - (mMenuContainer.getLeft() - getWidth()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initPeekScroller() {
|
||||
switch (getPosition()) {
|
||||
case RIGHT:
|
||||
case BOTTOM: {
|
||||
final int dx = -mPeekSize;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
final int dx = mPeekSize;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
onOffsetPixelsChanged((int) mOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GradientDrawable.Orientation getDropShadowOrientation() {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
return GradientDrawable.Orientation.TOP_BOTTOM;
|
||||
|
||||
case RIGHT:
|
||||
return GradientDrawable.Orientation.RIGHT_LEFT;
|
||||
|
||||
case BOTTOM:
|
||||
return GradientDrawable.Orientation.BOTTOM_TOP;
|
||||
|
||||
default:
|
||||
return GradientDrawable.Orientation.LEFT_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateDropShadowRect() {
|
||||
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
|
||||
final int dropShadowSize = (int) (mDropShadowSize * openRatio);
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mDropShadowRect.top = 0;
|
||||
mDropShadowRect.bottom = getHeight();
|
||||
mDropShadowRect.left = ViewHelper.getRight(mMenuContainer);
|
||||
mDropShadowRect.right = mDropShadowRect.left + dropShadowSize;
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mDropShadowRect.left = 0;
|
||||
mDropShadowRect.right = getWidth();
|
||||
mDropShadowRect.top = ViewHelper.getBottom(mMenuContainer);
|
||||
mDropShadowRect.bottom = mDropShadowRect.top + dropShadowSize;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mDropShadowRect.top = 0;
|
||||
mDropShadowRect.bottom = getHeight();
|
||||
mDropShadowRect.right = ViewHelper.getLeft(mMenuContainer);
|
||||
mDropShadowRect.left = mDropShadowRect.right - dropShadowSize;
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mDropShadowRect.left = 0;
|
||||
mDropShadowRect.right = getWidth();
|
||||
mDropShadowRect.bottom = ViewHelper.getTop(mMenuContainer);
|
||||
mDropShadowRect.top = mDropShadowRect.bottom - dropShadowSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startLayerTranslation() {
|
||||
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
|
||||
mLayerTypeHardware = true;
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopLayerTranslation() {
|
||||
if (mLayerTypeHardware) {
|
||||
mLayerTypeHardware = false;
|
||||
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
|
||||
mContentContainer.layout(0, 0, width, height);
|
||||
|
||||
if (USE_TRANSLATIONS) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(0, 0, mMenuSize, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width - mMenuSize, 0, width, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, 0, width, mMenuSize);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height - mMenuSize, width, height);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
final int menuSize = mMenuSize;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(-menuSize + offsetPixels, 0, offsetPixels, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width + offsetPixels, 0, width + menuSize + offsetPixels, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, -menuSize + offsetPixels, width, offsetPixels);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height + offsetPixels, width, height + menuSize + offsetPixels);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new IllegalStateException("Must measure with an exact size");
|
||||
}
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (mOffsetPixels == -1) openMenu(false);
|
||||
|
||||
int menuWidthMeasureSpec;
|
||||
int menuHeightMeasureSpec;
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, mMenuSize);
|
||||
break;
|
||||
|
||||
default:
|
||||
// LEFT/RIGHT
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
}
|
||||
mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
|
||||
|
||||
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
updateTouchAreaSize();
|
||||
}
|
||||
|
||||
private boolean isContentTouch(int x, int y) {
|
||||
boolean contentTouch = false;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
contentTouch = ViewHelper.getRight(mMenuContainer) < x;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
contentTouch = ViewHelper.getLeft(mMenuContainer) > x;
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
contentTouch = ViewHelper.getBottom(mMenuContainer) < y;
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
contentTouch = ViewHelper.getTop(mMenuContainer) > y;
|
||||
break;
|
||||
}
|
||||
|
||||
return contentTouch;
|
||||
}
|
||||
|
||||
protected boolean onDownAllowDrag(int x, int y) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionX <= mOffsetPixels);
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
final int initialMotionX = (int) mInitialMotionX;
|
||||
|
||||
return (!mMenuVisible && initialMotionX >= width - mTouchSize)
|
||||
|| (mMenuVisible && initialMotionX >= width + mOffsetPixels);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY <= mOffsetPixels);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY >= height + mOffsetPixels);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy) {
|
||||
if (mMenuVisible && mTouchMode == TOUCH_MODE_FULLSCREEN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0)) // Drawer closed
|
||||
|| (mMenuVisible && x <= mOffsetPixels) // Drawer open
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible); // Drawer revealed
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0))
|
||||
|| (mMenuVisible && x >= width - mOffsetPixels)
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize && (dy > 0))
|
||||
|| (mMenuVisible && x <= mOffsetPixels)
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (dy < 0))
|
||||
|| (mMenuVisible && x >= height - mOffsetPixels)
|
||||
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onMoveEvent(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dy, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dy, 0), -mMenuSize));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onUpEvent(int x, int y) {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT: {
|
||||
final int width = getWidth();
|
||||
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BOTTOM: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity < 0 ? -mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean checkTouchSlop(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
return Math.abs(dy) > mTouchSlop && Math.abs(dy) > Math.abs(dx);
|
||||
|
||||
default:
|
||||
return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopAnimation() {
|
||||
super.stopAnimation();
|
||||
removeCallbacks(mRevealRunnable);
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
removeCallbacks(mRevealRunnable);
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
|
||||
openMenu();
|
||||
} else {
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
|
||||
setOffsetPixels(0);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
setDrawerState(STATE_CLOSED);
|
||||
mIsDragging = false;
|
||||
}
|
||||
|
||||
// Always intercept events over the content while menu is visible.
|
||||
if (mMenuVisible) {
|
||||
int index = 0;
|
||||
if (mActivePointerId != INVALID_POINTER) {
|
||||
index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
}
|
||||
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
if (isContentTouch(x, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action != MotionEvent.ACTION_DOWN && mIsDragging) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
|
||||
if (!mMenuVisible && mInitialMotionX <= mPeekSize) {
|
||||
postDelayed(mRevealRunnable, 160);
|
||||
}
|
||||
|
||||
mIsDragging = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int activePointerId = mActivePointerId;
|
||||
if (activePointerId == INVALID_POINTER) {
|
||||
// If we don't have a valid id, the touch down wasn't on content.
|
||||
break;
|
||||
}
|
||||
|
||||
final int pointerIndex = ev.findPointerIndex(activePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (Math.abs(dx) >= mTouchSlop || Math.abs(dy) >= mTouchSlop) {
|
||||
removeCallbacks(mRevealRunnable);
|
||||
endPeek();
|
||||
}
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible)
|
||||
&& canChildrenScroll((int) dx, (int) dy, (int) x, (int) y)) {
|
||||
endDrag(); // Release the velocity tracker
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
endPeek();
|
||||
stopAnimation();
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
|
||||
if (!mMenuVisible && mLastMotionX <= mPeekSize) {
|
||||
postDelayed(mRevealRunnable, 160);
|
||||
}
|
||||
|
||||
startLayerTranslation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mIsDragging) {
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
endPeek();
|
||||
stopAnimation();
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
} else {
|
||||
mInitialMotionX = x;
|
||||
mInitialMotionY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsDragging) {
|
||||
startLayerTranslation();
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
onMoveEvent(dx, dy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP: {
|
||||
removeCallbacks(mRevealRunnable);
|
||||
int index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
onUpEvent(x, y);
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
mLastMotionX = ev.getX(index);
|
||||
mLastMotionY = ev.getY(index);
|
||||
mActivePointerId = ev.getPointerId(index);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPointerUp(MotionEvent ev) {
|
||||
final int pointerIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mLastMotionX = ev.getX(newPointerIndex);
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class PeekInterpolator implements Interpolator {
|
||||
private static final SinusoidalInterpolator SINUSOIDAL_INTERPOLATOR = new SinusoidalInterpolator();
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
float result;
|
||||
|
||||
if (input < 1.f / 3.f) {
|
||||
result = SINUSOIDAL_INTERPOLATOR.getInterpolation(input * 3);
|
||||
|
||||
} else if (input > 2.f / 3.f) {
|
||||
final float val = ((input + 1.f / 3.f) - 1.f) * 3.f;
|
||||
result = 1.f - SINUSOIDAL_INTERPOLATOR.getInterpolation(val);
|
||||
|
||||
} else {
|
||||
result = 1.f;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
* Enums used for positioning the drawer.
|
||||
*/
|
||||
public enum Position {
|
||||
// Positions the drawer to the left of the content.
|
||||
LEFT(0),
|
||||
|
||||
// Positions the drawer above the content.
|
||||
TOP(1),
|
||||
|
||||
// Positions the drawer to the right of the content.
|
||||
RIGHT(2),
|
||||
|
||||
// Positions the drawer below the content.
|
||||
BOTTOM(3),
|
||||
|
||||
/**
|
||||
* Position the drawer at the start edge. This will position the drawer to the {@link #LEFT} with LTR languages and
|
||||
* {@link #RIGHT} with RTL languages.
|
||||
*/
|
||||
START(4),
|
||||
|
||||
/**
|
||||
* Position the drawer at the end edge. This will position the drawer to the {@link #RIGHT} with LTR languages and
|
||||
* {@link #LEFT} with RTL languages.
|
||||
*/
|
||||
END(5);
|
||||
|
||||
final int mValue;
|
||||
|
||||
Position(int value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
private static final SparseArray<Position> STRING_MAPPING = new SparseArray<Position>();
|
||||
|
||||
static {
|
||||
for (Position via : Position.values()) {
|
||||
STRING_MAPPING.put(via.mValue, via);
|
||||
}
|
||||
}
|
||||
|
||||
public static Position fromValue(int value) {
|
||||
return STRING_MAPPING.get(value);
|
||||
}
|
||||
}
|
@ -1,504 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
|
||||
/**
|
||||
* This class encapsulates scrolling. The duration of the scroll
|
||||
* can be passed in the constructor and specifies the maximum time that
|
||||
* the scrolling animation should take. Past this time, the scrolling is
|
||||
* automatically moved to its final stage and computeScrollOffset()
|
||||
* will always return false to indicate that scrolling is over.
|
||||
*/
|
||||
class Scroller {
|
||||
private int mMode;
|
||||
|
||||
private int mStartX;
|
||||
private int mStartY;
|
||||
private int mFinalX;
|
||||
private int mFinalY;
|
||||
|
||||
private int mMinX;
|
||||
private int mMaxX;
|
||||
private int mMinY;
|
||||
private int mMaxY;
|
||||
|
||||
private int mCurrX;
|
||||
private int mCurrY;
|
||||
private long mStartTime;
|
||||
private int mDuration;
|
||||
private float mDurationReciprocal;
|
||||
private float mDeltaX;
|
||||
private float mDeltaY;
|
||||
private boolean mFinished;
|
||||
private Interpolator mInterpolator;
|
||||
private boolean mFlywheel;
|
||||
|
||||
private float mVelocity;
|
||||
|
||||
private static final int DEFAULT_DURATION = 250;
|
||||
private static final int SCROLL_MODE = 0;
|
||||
private static final int FLING_MODE = 1;
|
||||
|
||||
private static final float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
|
||||
private static final float ALPHA = 800; // pixels / seconds
|
||||
private static final float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
|
||||
private static final float END_TENSION = 1.0f - START_TENSION;
|
||||
private static final int NB_SAMPLES = 100;
|
||||
private static final float[] SPLINE = new float[NB_SAMPLES + 1];
|
||||
|
||||
private float mDeceleration;
|
||||
private final float mPpi;
|
||||
|
||||
static {
|
||||
float xMin = 0.0f;
|
||||
for (int i = 0; i <= NB_SAMPLES; i++) {
|
||||
final float t = (float) i / NB_SAMPLES;
|
||||
float xMax = 1.0f;
|
||||
float x, tx, coef;
|
||||
while (true) {
|
||||
x = xMin + (xMax - xMin) / 2.0f;
|
||||
coef = 3.0f * x * (1.0f - x);
|
||||
tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
|
||||
if (Math.abs(tx - t) < 1E-5) break;
|
||||
if (tx > t) xMax = x;
|
||||
else xMin = x;
|
||||
}
|
||||
final float d = coef + x * x * x;
|
||||
SPLINE[i] = d;
|
||||
}
|
||||
SPLINE[NB_SAMPLES] = 1.0f;
|
||||
|
||||
// This controls the viscous fluid effect (how much of it)
|
||||
sViscousFluidScale = 8.0f;
|
||||
// must be set to 1.0 (used in viscousFluid())
|
||||
sViscousFluidNormalize = 1.0f;
|
||||
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
|
||||
}
|
||||
|
||||
private static float sViscousFluidScale;
|
||||
private static float sViscousFluidNormalize;
|
||||
|
||||
/**
|
||||
* Create a Scroller with the default duration and interpolator.
|
||||
*/
|
||||
public Scroller(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||
* null, the default (viscous) interpolator will be used. "Flywheel" behavior will
|
||||
* be in effect for apps targeting Honeycomb or newer.
|
||||
*/
|
||||
public Scroller(Context context, Interpolator interpolator) {
|
||||
this(context, interpolator,
|
||||
context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Scroller with the specified interpolator. If the interpolator is
|
||||
* null, the default (viscous) interpolator will be used. Specify whether or
|
||||
* not to support progressive "flywheel" behavior in flinging.
|
||||
*/
|
||||
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
|
||||
mFinished = true;
|
||||
mInterpolator = interpolator;
|
||||
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
|
||||
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
|
||||
mFlywheel = flywheel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of friction applied to flings. The default value
|
||||
* is {@link android.view.ViewConfiguration#getScrollFriction}.
|
||||
*
|
||||
* @param friction A scalar dimension-less value representing the coefficient of
|
||||
* friction.
|
||||
*/
|
||||
public final void setFriction(float friction) {
|
||||
mDeceleration = computeDeceleration(friction);
|
||||
}
|
||||
|
||||
private float computeDeceleration(float friction) {
|
||||
return SensorManager.GRAVITY_EARTH // g (m/s^2)
|
||||
* 39.37f // inch/meter
|
||||
* mPpi // pixels per inch
|
||||
* friction;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns whether the scroller has finished scrolling.
|
||||
*
|
||||
* @return True if the scroller has finished scrolling, false otherwise.
|
||||
*/
|
||||
public final boolean isFinished() {
|
||||
return mFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the finished field to a particular value.
|
||||
*
|
||||
* @param finished The new finished value.
|
||||
*/
|
||||
public final void forceFinished(boolean finished) {
|
||||
mFinished = finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how long the scroll event will take, in milliseconds.
|
||||
*
|
||||
* @return The duration of the scroll in milliseconds.
|
||||
*/
|
||||
public final int getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current X offset in the scroll.
|
||||
*
|
||||
* @return The new X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getCurrX() {
|
||||
return mCurrX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Y offset in the scroll.
|
||||
*
|
||||
* @return The new Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getCurrY() {
|
||||
return mCurrY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current velocity.
|
||||
*
|
||||
* @return The original velocity less the deceleration. Result may be
|
||||
* negative.
|
||||
*/
|
||||
public float getCurrVelocity() {
|
||||
return mVelocity - mDeceleration * timePassed() / 2000.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start X offset in the scroll.
|
||||
*
|
||||
* @return The start X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getStartX() {
|
||||
return mStartX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start Y offset in the scroll.
|
||||
*
|
||||
* @return The start Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getStartY() {
|
||||
return mStartY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final X offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getFinalX() {
|
||||
return mFinalX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns where the scroll will end. Valid only for "fling" scrolls.
|
||||
*
|
||||
* @return The final Y offset as an absolute distance from the origin.
|
||||
*/
|
||||
public final int getFinalY() {
|
||||
return mFinalY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when you want to know the new location. If it returns true,
|
||||
* the animation is not yet finished. loc will be altered to provide the
|
||||
* new location.
|
||||
*/
|
||||
public boolean computeScrollOffset() {
|
||||
if (mFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
|
||||
if (timePassed < mDuration) {
|
||||
switch (mMode) {
|
||||
case SCROLL_MODE:
|
||||
float x = timePassed * mDurationReciprocal;
|
||||
|
||||
if (mInterpolator == null)
|
||||
x = viscousFluid(x);
|
||||
else
|
||||
x = mInterpolator.getInterpolation(x);
|
||||
|
||||
mCurrX = mStartX + Math.round(x * mDeltaX);
|
||||
mCurrY = mStartY + Math.round(x * mDeltaY);
|
||||
break;
|
||||
case FLING_MODE:
|
||||
final float t = (float) timePassed / mDuration;
|
||||
final int index = (int) (NB_SAMPLES * t);
|
||||
final float tInf = (float) index / NB_SAMPLES;
|
||||
final float tSup = (float) (index + 1) / NB_SAMPLES;
|
||||
final float dInf = SPLINE[index];
|
||||
final float dSup = SPLINE[index + 1];
|
||||
final float distanceCoef = dInf + (t - tInf) / (tSup - tInf) * (dSup - dInf);
|
||||
|
||||
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
|
||||
// Pin to mMinX <= mCurrX <= mMaxX
|
||||
mCurrX = Math.min(mCurrX, mMaxX);
|
||||
mCurrX = Math.max(mCurrX, mMinX);
|
||||
|
||||
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
|
||||
// Pin to mMinY <= mCurrY <= mMaxY
|
||||
mCurrY = Math.min(mCurrY, mMaxY);
|
||||
mCurrY = Math.max(mCurrY, mMinY);
|
||||
|
||||
if (mCurrX == mFinalX && mCurrY == mFinalY) {
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mCurrX = mFinalX;
|
||||
mCurrY = mFinalY;
|
||||
mFinished = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling by providing a starting point and the distance to travel.
|
||||
* The scroll will use the default value of 250 milliseconds for the
|
||||
* duration.
|
||||
*
|
||||
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||
* numbers will scroll the content to the left.
|
||||
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||
* will scroll the content up.
|
||||
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||
* content to the left.
|
||||
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||
* content up.
|
||||
*/
|
||||
public void startScroll(int startX, int startY, int dx, int dy) {
|
||||
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling by providing a starting point and the distance to travel.
|
||||
*
|
||||
* @param startX Starting horizontal scroll offset in pixels. Positive
|
||||
* numbers will scroll the content to the left.
|
||||
* @param startY Starting vertical scroll offset in pixels. Positive numbers
|
||||
* will scroll the content up.
|
||||
* @param dx Horizontal distance to travel. Positive numbers will scroll the
|
||||
* content to the left.
|
||||
* @param dy Vertical distance to travel. Positive numbers will scroll the
|
||||
* content up.
|
||||
* @param duration Duration of the scroll in milliseconds.
|
||||
*/
|
||||
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
|
||||
mMode = SCROLL_MODE;
|
||||
mFinished = false;
|
||||
mDuration = duration;
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStartX = startX;
|
||||
mStartY = startY;
|
||||
mFinalX = startX + dx;
|
||||
mFinalY = startY + dy;
|
||||
mDeltaX = dx;
|
||||
mDeltaY = dy;
|
||||
mDurationReciprocal = 1.0f / (float) mDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start scrolling based on a fling gesture. The distance travelled will
|
||||
* depend on the initial velocity of the fling.
|
||||
*
|
||||
* @param startX Starting point of the scroll (X)
|
||||
* @param startY Starting point of the scroll (Y)
|
||||
* @param velocityX Initial velocity of the fling (X) measured in pixels per
|
||||
* second.
|
||||
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
|
||||
* second
|
||||
* @param minX Minimum X value. The scroller will not scroll past this
|
||||
* point.
|
||||
* @param maxX Maximum X value. The scroller will not scroll past this
|
||||
* point.
|
||||
* @param minY Minimum Y value. The scroller will not scroll past this
|
||||
* point.
|
||||
* @param maxY Maximum Y value. The scroller will not scroll past this
|
||||
* point.
|
||||
*/
|
||||
public void fling(int startX, int startY, int velocityX, int velocityY,
|
||||
int minX, int maxX, int minY, int maxY) {
|
||||
// Continue a scroll or fling in progress
|
||||
if (mFlywheel && !mFinished) {
|
||||
float oldVel = getCurrVelocity();
|
||||
|
||||
float dx = (float) (mFinalX - mStartX);
|
||||
float dy = (float) (mFinalY - mStartY);
|
||||
float hyp = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
float ndx = dx / hyp;
|
||||
float ndy = dy / hyp;
|
||||
|
||||
float oldVelocityX = ndx * oldVel;
|
||||
float oldVelocityY = ndy * oldVel;
|
||||
if (Math.signum(velocityX) == Math.signum(oldVelocityX)
|
||||
&& Math.signum(velocityY) == Math.signum(oldVelocityY)) {
|
||||
velocityX += oldVelocityX;
|
||||
velocityY += oldVelocityY;
|
||||
}
|
||||
}
|
||||
|
||||
mMode = FLING_MODE;
|
||||
mFinished = false;
|
||||
|
||||
float velocity = (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY);
|
||||
|
||||
mVelocity = velocity;
|
||||
final double l = Math.log(START_TENSION * velocity / ALPHA);
|
||||
mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
|
||||
mStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
mStartX = startX;
|
||||
mStartY = startY;
|
||||
|
||||
float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
|
||||
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
|
||||
|
||||
int totalDistance =
|
||||
(int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
|
||||
|
||||
mMinX = minX;
|
||||
mMaxX = maxX;
|
||||
mMinY = minY;
|
||||
mMaxY = maxY;
|
||||
|
||||
mFinalX = startX + Math.round(totalDistance * coeffX);
|
||||
// Pin to mMinX <= mFinalX <= mMaxX
|
||||
mFinalX = Math.min(mFinalX, mMaxX);
|
||||
mFinalX = Math.max(mFinalX, mMinX);
|
||||
|
||||
mFinalY = startY + Math.round(totalDistance * coeffY);
|
||||
// Pin to mMinY <= mFinalY <= mMaxY
|
||||
mFinalY = Math.min(mFinalY, mMaxY);
|
||||
mFinalY = Math.max(mFinalY, mMinY);
|
||||
}
|
||||
|
||||
static float viscousFluid(float x) {
|
||||
x *= sViscousFluidScale;
|
||||
if (x < 1.0f) {
|
||||
x -= (1.0f - (float) Math.exp(-x));
|
||||
} else {
|
||||
float start = 0.36787944117f; // 1/e == exp(-1)
|
||||
x = 1.0f - (float) Math.exp(1.0f - x);
|
||||
x = start + x * (1.0f - start);
|
||||
}
|
||||
x *= sViscousFluidNormalize;
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
|
||||
* aborting the animating cause the scroller to move to the final x and y
|
||||
* position
|
||||
*
|
||||
* @see #forceFinished(boolean)
|
||||
*/
|
||||
public void abortAnimation() {
|
||||
mCurrX = mFinalX;
|
||||
mCurrY = mFinalY;
|
||||
mFinished = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the scroll animation. This allows a running animation to scroll
|
||||
* further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
|
||||
*
|
||||
* @param extend Additional time to scroll in milliseconds.
|
||||
* @see #setFinalX(int)
|
||||
* @see #setFinalY(int)
|
||||
*/
|
||||
public void extendDuration(int extend) {
|
||||
int passed = timePassed();
|
||||
mDuration = passed + extend;
|
||||
mDurationReciprocal = 1.0f / mDuration;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed since the beginning of the scrolling.
|
||||
*
|
||||
* @return The elapsed time in milliseconds.
|
||||
*/
|
||||
public int timePassed() {
|
||||
return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the final position (X) for this scroller.
|
||||
*
|
||||
* @param newX The new X offset as an absolute distance from the origin.
|
||||
* @see #extendDuration(int)
|
||||
* @see #setFinalY(int)
|
||||
*/
|
||||
public void setFinalX(int newX) {
|
||||
mFinalX = newX;
|
||||
mDeltaX = mFinalX - mStartX;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the final position (Y) for this scroller.
|
||||
*
|
||||
* @param newY The new Y offset as an absolute distance from the origin.
|
||||
* @see #extendDuration(int)
|
||||
* @see #setFinalX(int)
|
||||
*/
|
||||
public void setFinalY(int newY) {
|
||||
mFinalY = newY;
|
||||
mDeltaY = mFinalY - mStartY;
|
||||
mFinished = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean isScrollingInDirection(float xvel, float yvel) {
|
||||
return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX)
|
||||
&& Math.signum(yvel) == Math.signum(mFinalY - mStartY);
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* Interpolator which, when drawn from 0 to 1, looks like half a sine-wave. Used for smoother opening/closing when
|
||||
* peeking at the drawer.
|
||||
*/
|
||||
class SinusoidalInterpolator implements Interpolator {
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
return (float) (0.5f + 0.5f * Math.sin(input * Math.PI - Math.PI / 2.f));
|
||||
}
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class SlideDrawable extends Drawable implements Drawable.Callback {
|
||||
|
||||
private Drawable mWrapped;
|
||||
private float mOffset;
|
||||
|
||||
private final Rect mTmpRect = new Rect();
|
||||
|
||||
private boolean mIsRtl;
|
||||
|
||||
public SlideDrawable(Drawable wrapped) {
|
||||
mWrapped = wrapped;
|
||||
}
|
||||
|
||||
public void setOffset(float offset) {
|
||||
mOffset = offset;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getOffset() {
|
||||
return mOffset;
|
||||
}
|
||||
|
||||
void setIsRtl(boolean isRtl) {
|
||||
mIsRtl = isRtl;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mWrapped.copyBounds(mTmpRect);
|
||||
canvas.save();
|
||||
if (mIsRtl) {
|
||||
canvas.translate(1.f / 3 * mTmpRect.width() * mOffset, 0);
|
||||
} else {
|
||||
canvas.translate(1.f / 3 * mTmpRect.width() * -mOffset, 0);
|
||||
}
|
||||
mWrapped.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangingConfigurations(int configs) {
|
||||
mWrapped.setChangingConfigurations(configs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return mWrapped.getChangingConfigurations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDither(boolean dither) {
|
||||
mWrapped.setDither(dither);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filter) {
|
||||
mWrapped.setFilterBitmap(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mWrapped.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mWrapped.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(int color, PorterDuff.Mode mode) {
|
||||
mWrapped.setColorFilter(color, mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearColorFilter() {
|
||||
mWrapped.clearColorFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return mWrapped.isStateful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setState(int[] stateSet) {
|
||||
return mWrapped.setState(stateSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getState() {
|
||||
return mWrapped.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getCurrent() {
|
||||
return mWrapped.getCurrent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
return super.setVisible(visible, restart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mWrapped.getOpacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getTransparentRegion() {
|
||||
return mWrapped.getTransparentRegion();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
mWrapped.setState(state);
|
||||
return super.onStateChange(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mWrapped.setBounds(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mWrapped.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mWrapped.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumWidth() {
|
||||
return mWrapped.getMinimumWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumHeight() {
|
||||
return mWrapped.getMinimumHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(Rect padding) {
|
||||
return mWrapped.getPadding(padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
return super.getConstantState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable who) {
|
||||
if (who == mWrapped) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable who, Runnable what, long when) {
|
||||
if (who == mWrapped) {
|
||||
scheduleSelf(what, when);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable who, Runnable what) {
|
||||
if (who == mWrapped) {
|
||||
unscheduleSelf(what);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,704 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
|
||||
public class SlidingDrawer extends DraggableDrawer {
|
||||
SlidingDrawer(Activity activity, int dragMode) {
|
||||
super(activity, dragMode);
|
||||
}
|
||||
|
||||
public SlidingDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SlidingDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu(boolean animate) {
|
||||
int animateTo = 0;
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case TOP:
|
||||
animateTo = mMenuSize;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
case BOTTOM:
|
||||
animateTo = -mMenuSize;
|
||||
break;
|
||||
}
|
||||
|
||||
animateOffsetTo(animateTo, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeMenu(boolean animate) {
|
||||
animateOffsetTo(0, 0, animate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOffsetPixelsChanged(int offsetPixels) {
|
||||
if (USE_TRANSLATIONS) {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
mContentContainer.setTranslationY(offsetPixels);
|
||||
break;
|
||||
|
||||
default:
|
||||
mContentContainer.setTranslationX(offsetPixels);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop());
|
||||
break;
|
||||
|
||||
default:
|
||||
mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
offsetMenu(offsetPixels);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initPeekScroller() {
|
||||
switch (getPosition()) {
|
||||
case RIGHT:
|
||||
case BOTTOM: {
|
||||
final int dx = -mMenuSize / 3;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
final int dx = mMenuSize / 3;
|
||||
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
onOffsetPixelsChanged((int) mOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(Canvas canvas) {
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuOverlay.setBounds(0, 0, offsetPixels, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuOverlay.setBounds(width + offsetPixels, 0, width, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuOverlay.setBounds(0, 0, width, offsetPixels);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuOverlay.setBounds(0, height + offsetPixels, width, height);
|
||||
break;
|
||||
}
|
||||
|
||||
mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
|
||||
mMenuOverlay.draw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
|
||||
if (USE_TRANSLATIONS) {
|
||||
mContentContainer.layout(0, 0, width, height);
|
||||
} else {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
if (getPosition() == Position.LEFT || getPosition() == Position.RIGHT) {
|
||||
mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height);
|
||||
} else {
|
||||
mContentContainer.layout(0, offsetPixels, width, height + offsetPixels);
|
||||
}
|
||||
}
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(0, 0, mMenuSize, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width - mMenuSize, 0, width, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, 0, width, mMenuSize);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height - mMenuSize, width, height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Offsets the menu relative to its original position based on the position of the content.
|
||||
*
|
||||
* @param offsetPixels The number of pixels the content if offset.
|
||||
*/
|
||||
private void offsetMenu(int offsetPixels) {
|
||||
if (!mOffsetMenu || mMenuSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int width = getWidth();
|
||||
final int height = getHeight();
|
||||
|
||||
final int menuSize = mMenuSize;
|
||||
final int sign = (int) (mOffsetPixels / Math.abs(mOffsetPixels));
|
||||
final float openRatio = Math.abs(mOffsetPixels) / menuSize;
|
||||
final int offset = (int) (-0.25f * ((1.0f - openRatio) * menuSize) * sign);
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels > 0) {
|
||||
mMenuContainer.setTranslationX(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationX(-menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
mMenuContainer.offsetLeftAndRight(offset - mMenuContainer.getLeft());
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels != 0) {
|
||||
mMenuContainer.setTranslationX(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationX(menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
final int oldOffset = mMenuContainer.getRight() - width;
|
||||
final int offsetBy = offset - oldOffset;
|
||||
mMenuContainer.offsetLeftAndRight(offsetBy);
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels > 0) {
|
||||
mMenuContainer.setTranslationY(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationY(-menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
mMenuContainer.offsetTopAndBottom(offset - mMenuContainer.getTop());
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BOTTOM: {
|
||||
if (USE_TRANSLATIONS) {
|
||||
if (offsetPixels != 0) {
|
||||
mMenuContainer.setTranslationY(offset);
|
||||
} else {
|
||||
mMenuContainer.setTranslationY(menuSize);
|
||||
}
|
||||
|
||||
} else {
|
||||
final int oldOffset = mMenuContainer.getBottom() - height;
|
||||
final int offsetBy = offset - oldOffset;
|
||||
mMenuContainer.offsetTopAndBottom(offsetBy);
|
||||
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new IllegalStateException("Must measure with an exact size");
|
||||
}
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (mOffsetPixels == -1) openMenu(false);
|
||||
|
||||
int menuWidthMeasureSpec;
|
||||
int menuHeightMeasureSpec;
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, mMenuSize);
|
||||
break;
|
||||
|
||||
default:
|
||||
// LEFT/RIGHT
|
||||
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
|
||||
menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
}
|
||||
mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
|
||||
|
||||
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
|
||||
final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
|
||||
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
updateTouchAreaSize();
|
||||
}
|
||||
|
||||
private boolean isContentTouch(int x, int y) {
|
||||
boolean contentTouch = false;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
contentTouch = ViewHelper.getLeft(mContentContainer) < x;
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
contentTouch = ViewHelper.getRight(mContentContainer) > x;
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
contentTouch = ViewHelper.getTop(mContentContainer) < y;
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
contentTouch = ViewHelper.getBottom(mContentContainer) > y;
|
||||
break;
|
||||
}
|
||||
|
||||
return contentTouch;
|
||||
}
|
||||
|
||||
protected boolean onDownAllowDrag(int x, int y) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionX >= mOffsetPixels);
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
final int initialMotionX = (int) mInitialMotionX;
|
||||
|
||||
return (!mMenuVisible && initialMotionX >= width - mTouchSize)
|
||||
|| (mMenuVisible && initialMotionX <= width + mOffsetPixels);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY >= mOffsetPixels);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
|
||||
|| (mMenuVisible && mInitialMotionY <= height + mOffsetPixels);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0))
|
||||
|| (mMenuVisible && x >= mOffsetPixels);
|
||||
|
||||
case RIGHT:
|
||||
final int width = getWidth();
|
||||
return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0))
|
||||
|| (mMenuVisible && x <= width + mOffsetPixels);
|
||||
|
||||
case TOP:
|
||||
return (!mMenuVisible && mInitialMotionY <= mTouchSize && (dy > 0))
|
||||
|| (mMenuVisible && y >= mOffsetPixels);
|
||||
|
||||
case BOTTOM:
|
||||
final int height = getHeight();
|
||||
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (dy < 0))
|
||||
|| (mMenuVisible && y <= height + mOffsetPixels);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onMoveEvent(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dy, 0), mMenuSize));
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dy, 0), -mMenuSize));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onUpEvent(int x, int y) {
|
||||
final int offsetPixels = (int) mOffsetPixels;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && x > offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && y > offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RIGHT: {
|
||||
final int width = getWidth();
|
||||
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
|
||||
mLastMotionX = x;
|
||||
animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && x < width + offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case BOTTOM: {
|
||||
if (mIsDragging) {
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
|
||||
mLastMotionY = y;
|
||||
animateOffsetTo(initialVelocity < 0 ? -mMenuSize : 0, initialVelocity, true);
|
||||
|
||||
// Close the menu when content is clicked while the menu is visible.
|
||||
} else if (mMenuVisible && y < getHeight() + offsetPixels) {
|
||||
closeMenu();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean checkTouchSlop(float dx, float dy) {
|
||||
switch (getPosition()) {
|
||||
case TOP:
|
||||
case BOTTOM:
|
||||
return Math.abs(dy) > mTouchSlop && Math.abs(dy) > Math.abs(dx);
|
||||
|
||||
default:
|
||||
return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
|
||||
if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
|
||||
openMenu();
|
||||
} else {
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
|
||||
setOffsetPixels(0);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
setDrawerState(STATE_CLOSED);
|
||||
mIsDragging = false;
|
||||
}
|
||||
|
||||
// Always intercept events over the content while menu is visible.
|
||||
if (mMenuVisible) {
|
||||
int index = 0;
|
||||
if (mActivePointerId != INVALID_POINTER) {
|
||||
index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
}
|
||||
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
if (isContentTouch(x, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action != MotionEvent.ACTION_DOWN && mIsDragging) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
mIsDragging = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int activePointerId = mActivePointerId;
|
||||
if (activePointerId == INVALID_POINTER) {
|
||||
// If we don't have a valid id, the touch down wasn't on content.
|
||||
break;
|
||||
}
|
||||
|
||||
final int pointerIndex = ev.findPointerIndex(activePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible)
|
||||
&& canChildrenScroll((int) dx, (int) dy, (int) x, (int) y)) {
|
||||
endDrag(); // Release the velocity tracker
|
||||
requestDisallowInterceptTouchEvent(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
|
||||
return false;
|
||||
}
|
||||
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
|
||||
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
|
||||
mVelocityTracker.addMovement(ev);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mLastMotionX = mInitialMotionX = ev.getX();
|
||||
mLastMotionY = mInitialMotionY = ev.getY();
|
||||
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
|
||||
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
if (allowDrag) {
|
||||
stopAnimation();
|
||||
endPeek();
|
||||
startLayerTranslation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
mIsDragging = false;
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
endDrag();
|
||||
closeMenu(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mIsDragging) {
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
if (checkTouchSlop(dx, dy)) {
|
||||
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
|
||||
|
||||
if (allowDrag) {
|
||||
setDrawerState(STATE_DRAGGING);
|
||||
mIsDragging = true;
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
} else {
|
||||
mInitialMotionX = x;
|
||||
mInitialMotionY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsDragging) {
|
||||
startLayerTranslation();
|
||||
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float dx = x - mLastMotionX;
|
||||
final float y = ev.getY(pointerIndex);
|
||||
final float dy = y - mLastMotionY;
|
||||
|
||||
mLastMotionX = x;
|
||||
mLastMotionY = y;
|
||||
onMoveEvent(dx, dy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP: {
|
||||
int index = ev.findPointerIndex(mActivePointerId);
|
||||
index = index == -1 ? 0 : index;
|
||||
final int x = (int) ev.getX(index);
|
||||
final int y = (int) ev.getY(index);
|
||||
onUpEvent(x, y);
|
||||
mActivePointerId = INVALID_POINTER;
|
||||
mIsDragging = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
mLastMotionX = ev.getX(index);
|
||||
mLastMotionY = ev.getY(index);
|
||||
mActivePointerId = ev.getPointerId(index);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
onPointerUp(ev);
|
||||
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
|
||||
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPointerUp(MotionEvent ev) {
|
||||
final int pointerIndex = ev.getActionIndex();
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mLastMotionX = ev.getX(newPointerIndex);
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class SmoothInterpolator implements Interpolator {
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float t) {
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class StaticDrawer extends MenuDrawer {
|
||||
|
||||
public StaticDrawer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public StaticDrawer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public StaticDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super.initDrawer(context, attrs, defStyle);
|
||||
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||
|
||||
mIsStatic = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawOverlay(Canvas canvas) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOffsetPixelsChanged(int offsetPixels) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
mMenuContainer.layout(0, 0, mMenuSize, height);
|
||||
mContentContainer.layout(mMenuSize, 0, width, height);
|
||||
break;
|
||||
|
||||
case RIGHT:
|
||||
mMenuContainer.layout(width - mMenuSize, 0, width, height);
|
||||
mContentContainer.layout(0, 0, width - mMenuSize, height);
|
||||
break;
|
||||
|
||||
case TOP:
|
||||
mMenuContainer.layout(0, 0, width, mMenuSize);
|
||||
mContentContainer.layout(0, mMenuSize, width, height);
|
||||
break;
|
||||
|
||||
case BOTTOM:
|
||||
mMenuContainer.layout(0, height - mMenuSize, width, height);
|
||||
mContentContainer.layout(0, 0, width, height - mMenuSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
throw new IllegalStateException("Must measure with an exact size");
|
||||
}
|
||||
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
switch (getPosition()) {
|
||||
case LEFT:
|
||||
case RIGHT: {
|
||||
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
|
||||
|
||||
final int menuWidth = mMenuSize;
|
||||
final int menuWidthMeasureSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY);
|
||||
|
||||
final int contentWidth = width - menuWidth;
|
||||
final int contentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
|
||||
|
||||
mContentContainer.measure(contentWidthMeasureSpec, childHeightMeasureSpec);
|
||||
mMenuContainer.measure(menuWidthMeasureSpec, childHeightMeasureSpec);
|
||||
break;
|
||||
}
|
||||
|
||||
case TOP:
|
||||
case BOTTOM: {
|
||||
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
||||
|
||||
final int menuHeight = mMenuSize;
|
||||
final int menuHeightMeasureSpec = MeasureSpec.makeMeasureSpec(menuHeight, MeasureSpec.EXACTLY);
|
||||
|
||||
final int contentHeight = height - menuHeight;
|
||||
final int contentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
|
||||
|
||||
mContentContainer.measure(childWidthMeasureSpec, contentHeightMeasureSpec);
|
||||
mMenuContainer.measure(childWidthMeasureSpec, menuHeightMeasureSpec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleMenu(boolean animate) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openMenu(boolean animate) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeMenu(boolean animate) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMenuVisible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuSize(int size) {
|
||||
mMenuSize = size;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOffsetMenuEnabled(boolean offsetMenu) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getOffsetMenuEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekDrawer() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekDrawer(long delay) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekDrawer(long startDelay, long delay) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHardwareLayerEnabled(boolean enabled) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTouchMode() {
|
||||
return TOUCH_MODE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTouchMode(int mode) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTouchBezelSize(int size) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTouchBezelSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlideDrawable(int drawableRes) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSlideDrawable(Drawable drawable) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupUpIndicator(Activity activity) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDrawerIndicatorEnabled(boolean enabled) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDrawerIndicatorEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package net.simonvt.menudrawer;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
final class ViewHelper {
|
||||
|
||||
private ViewHelper() {
|
||||
}
|
||||
|
||||
public static int getLeft(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getLeft() + v.getTranslationX());
|
||||
}
|
||||
|
||||
return v.getLeft();
|
||||
}
|
||||
|
||||
public static int getTop(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getTop() + v.getTranslationY());
|
||||
}
|
||||
|
||||
return v.getTop();
|
||||
}
|
||||
|
||||
public static int getRight(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getRight() + v.getTranslationX());
|
||||
}
|
||||
|
||||
return v.getRight();
|
||||
}
|
||||
|
||||
public static int getBottom(View v) {
|
||||
if (MenuDrawer.USE_TRANSLATIONS) {
|
||||
return (int) (v.getBottom() + v.getTranslationY());
|
||||
}
|
||||
|
||||
return v.getBottom();
|
||||
}
|
||||
|
||||
public static int getLayoutDirection(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return v.getLayoutDirection();
|
||||
}
|
||||
|
||||
return View.LAYOUT_DIRECTION_LTR;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package net.simonvt.menudrawer.compat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class ActionBarHelper {
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
private Activity mActivity;
|
||||
|
||||
private Object mIndicatorInfo;
|
||||
|
||||
private boolean mUsesCompat;
|
||||
|
||||
public ActionBarHelper(Activity activity) {
|
||||
mActivity = activity;
|
||||
|
||||
try {
|
||||
Class clazz = activity.getClass();
|
||||
Method m = clazz.getMethod("getSupportActionBar");
|
||||
mUsesCompat = true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (DEBUG) {
|
||||
Timber.e(e,
|
||||
"Activity " + activity.getClass().getSimpleName() + " does not use a compatibility action bar");
|
||||
}
|
||||
}
|
||||
|
||||
mIndicatorInfo = getIndicatorInfo();
|
||||
}
|
||||
|
||||
private Object getIndicatorInfo() {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return ActionBarHelperCompat.getIndicatorInfo(mActivity);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return ActionBarHelperNative.getIndicatorInfo(mActivity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setActionBarUpIndicator(Drawable drawable, int contentDesc) {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
ActionBarHelperCompat.setActionBarUpIndicator(mIndicatorInfo, mActivity, drawable, contentDesc);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
ActionBarHelperNative.setActionBarUpIndicator(mIndicatorInfo, mActivity, drawable, contentDesc);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActionBarDescription(int contentDesc) {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
ActionBarHelperCompat.setActionBarDescription(mIndicatorInfo, mActivity, contentDesc);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
ActionBarHelperNative.setActionBarDescription(mIndicatorInfo, mActivity, contentDesc);
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable getThemeUpIndicator() {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return ActionBarHelperCompat.getThemeUpIndicator(mIndicatorInfo);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return ActionBarHelperNative.getThemeUpIndicator(mIndicatorInfo, mActivity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setDisplayShowHomeAsUpEnabled(boolean enabled) {
|
||||
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
ActionBarHelperCompat.setDisplayHomeAsUpEnabled(mIndicatorInfo, enabled);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
ActionBarHelperNative.setDisplayHomeAsUpEnabled(mActivity, enabled);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package net.simonvt.menudrawer.compat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import timber.log.Timber;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
final class ActionBarHelperCompat {
|
||||
|
||||
private ActionBarHelperCompat() {
|
||||
}
|
||||
|
||||
public static void setActionBarUpIndicator(Object info, Activity activity, Drawable drawable, int contentDescRes) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mUpIndicatorView != null) {
|
||||
sii.mUpIndicatorView.setImageDrawable(drawable);
|
||||
final String contentDescription = contentDescRes == 0 ? null : activity.getString(contentDescRes);
|
||||
sii.mUpIndicatorView.setContentDescription(contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setActionBarDescription(Object info, Activity activity, int contentDescRes) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mUpIndicatorView != null) {
|
||||
final String contentDescription = contentDescRes == 0 ? null : activity.getString(contentDescRes);
|
||||
sii.mUpIndicatorView.setContentDescription(contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
public static Drawable getThemeUpIndicator(Object info) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mUpIndicatorView != null) {
|
||||
return sii.mUpIndicatorView.getDrawable();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getIndicatorInfo(Activity activity) {
|
||||
return new SetIndicatorInfo(activity);
|
||||
}
|
||||
|
||||
public static void setDisplayHomeAsUpEnabled(Object info, boolean enabled) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.mHomeAsUpEnabled != null) {
|
||||
try {
|
||||
sii.mHomeAsUpEnabled.invoke(sii.mActionBar, enabled);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) {
|
||||
Timber.e(t, "Unable to call setHomeAsUpEnabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetIndicatorInfo {
|
||||
|
||||
public ImageView mUpIndicatorView;
|
||||
public Object mActionBar;
|
||||
public Method mHomeAsUpEnabled;
|
||||
|
||||
SetIndicatorInfo(Activity activity) {
|
||||
try {
|
||||
String appPackage = activity.getPackageName();
|
||||
|
||||
try {
|
||||
// Attempt to find ActionBarSherlock up indicator
|
||||
final int homeId = activity.getResources().getIdentifier("abs__home", "id", appPackage);
|
||||
View v = activity.findViewById(homeId);
|
||||
ViewGroup parent = (ViewGroup) v.getParent();
|
||||
final int upId = activity.getResources().getIdentifier("abs__up", "id", appPackage);
|
||||
mUpIndicatorView = (ImageView) parent.findViewById(upId);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) {
|
||||
Timber.e(t,"ABS action bar not found");
|
||||
}
|
||||
}
|
||||
|
||||
if (mUpIndicatorView == null) {
|
||||
// Attempt to find AppCompat up indicator
|
||||
final int homeId = activity.getResources().getIdentifier("home", "id", appPackage);
|
||||
View v = activity.findViewById(homeId);
|
||||
ViewGroup parent = (ViewGroup) v.getParent();
|
||||
final int upId = activity.getResources().getIdentifier("up", "id", appPackage);
|
||||
mUpIndicatorView = (ImageView) parent.findViewById(upId);
|
||||
}
|
||||
|
||||
Class supportActivity = activity.getClass();
|
||||
Method getActionBar = supportActivity.getMethod("getSupportActionBar");
|
||||
|
||||
mActionBar = getActionBar.invoke(activity, (Object)null);
|
||||
Class supportActionBar = mActionBar.getClass();
|
||||
mHomeAsUpEnabled = supportActionBar.getMethod("setDisplayHomeAsUpEnabled", Boolean.TYPE);
|
||||
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) {
|
||||
Timber.e(t, "Unable to init SetIndicatorInfo for ABS");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package net.simonvt.menudrawer.compat;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import timber.log.Timber;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
final class ActionBarHelperNative {
|
||||
|
||||
private ActionBarHelperNative() {
|
||||
}
|
||||
|
||||
private static final int[] THEME_ATTRS = new int[] {
|
||||
android.R.attr.homeAsUpIndicator
|
||||
};
|
||||
|
||||
public static void setActionBarUpIndicator(Object info, Activity activity, Drawable drawable, int contentDescRes) {
|
||||
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.setHomeAsUpIndicator != null) {
|
||||
try {
|
||||
final ActionBar actionBar = activity.getActionBar();
|
||||
sii.setHomeAsUpIndicator.invoke(actionBar, drawable);
|
||||
sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) Timber.e(t, "Couldn't set home-as-up indicator via JB-MR2 API");
|
||||
}
|
||||
} else if (sii.upIndicatorView != null) {
|
||||
sii.upIndicatorView.setImageDrawable(drawable);
|
||||
} else {
|
||||
if (ActionBarHelper.DEBUG) Timber.e("Couldn't set home-as-up indicator");
|
||||
}
|
||||
}
|
||||
|
||||
public static void setActionBarDescription(Object info, Activity activity, int contentDescRes) {
|
||||
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
|
||||
if (sii.setHomeAsUpIndicator != null) {
|
||||
try {
|
||||
final ActionBar actionBar = activity.getActionBar();
|
||||
sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
|
||||
} catch (Throwable t) {
|
||||
if (ActionBarHelper.DEBUG) Timber.e(t, "Couldn't set content description via JB-MR2 API");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Drawable getThemeUpIndicator(Object info, Activity activity) {
|
||||
final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
|
||||
final Drawable result = a.getDrawable(0);
|
||||
a.recycle();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object getIndicatorInfo(Activity activity) {
|
||||
return new SetIndicatorInfo(activity);
|
||||
}
|
||||
|
||||
public static void setDisplayHomeAsUpEnabled(Activity activity, boolean b) {
|
||||
ActionBar actionBar = activity.getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(b);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetIndicatorInfo {
|
||||
|
||||
public Method setHomeAsUpIndicator;
|
||||
public Method setHomeActionContentDescription;
|
||||
public ImageView upIndicatorView;
|
||||
|
||||
SetIndicatorInfo(Activity activity) {
|
||||
try {
|
||||
setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod("setHomeAsUpIndicator", Drawable.class);
|
||||
setHomeActionContentDescription = ActionBar.class.getDeclaredMethod(
|
||||
"setHomeActionContentDescription", Integer.TYPE);
|
||||
|
||||
// If we got the method we won't need the stuff below.
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
// Oh well. We'll use the other mechanism below instead.
|
||||
}
|
||||
|
||||
final View home = activity.findViewById(android.R.id.home);
|
||||
if (home == null) {
|
||||
// Action bar doesn't have a known configuration, an OEM messed with things.
|
||||
return;
|
||||
}
|
||||
|
||||
final ViewGroup parent = (ViewGroup) home.getParent();
|
||||
final int childCount = parent.getChildCount();
|
||||
if (childCount != 2) {
|
||||
// No idea which one will be the right one, an OEM messed with things.
|
||||
return;
|
||||
}
|
||||
|
||||
final View first = parent.getChildAt(0);
|
||||
final View second = parent.getChildAt(1);
|
||||
final View up = first.getId() == android.R.id.home ? second : first;
|
||||
|
||||
if (up instanceof ImageView) {
|
||||
// Jackpot! (Probably...)
|
||||
upIndicatorView = (ImageView) up;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!-- Reference to a style for the menu drawer. -->
|
||||
<attr name="menuDrawerStyle" format="reference" />
|
||||
|
||||
<!-- Styleables used for styling the menu drawer. -->
|
||||
<declare-styleable name="MenuDrawer">
|
||||
|
||||
<!-- Drawable to use for the background of the content. -->
|
||||
<attr name="mdContentBackground" format="reference" />
|
||||
|
||||
<!-- Drawable to use for the background of the menu. -->
|
||||
<attr name="mdMenuBackground" format="reference" />
|
||||
|
||||
<!-- The size of the menu. -->
|
||||
<attr name="mdMenuSize" format="dimension" />
|
||||
|
||||
<!-- Drawable used as indicator for the active view. -->
|
||||
<attr name="mdActiveIndicator" format="reference" />
|
||||
|
||||
<!-- Defines whether the content will have a dropshadow onto the menu. Default is true. -->
|
||||
<attr name="mdDropShadowEnabled" format="boolean" />
|
||||
|
||||
<!-- The size of the drop shadow. Default is 6dp -->
|
||||
<attr name="mdDropShadowSize" format="dimension" />
|
||||
|
||||
<!-- The color of the drop shadow. Default is #FF000000. -->
|
||||
<attr name="mdDropShadowColor" format="color" />
|
||||
|
||||
<!-- Drawable used for the drop shadow. -->
|
||||
<attr name="mdDropShadow" format="reference" />
|
||||
|
||||
<!-- The touch bezel size. -->
|
||||
<attr name="mdTouchBezelSize" format="dimension" />
|
||||
|
||||
<!-- Whether the indicator should be animated between active views. -->
|
||||
<attr name="mdAllowIndicatorAnimation" format="boolean" />
|
||||
|
||||
<!-- The maximum animation duration -->
|
||||
<attr name="mdMaxAnimationDuration" format="integer" />
|
||||
|
||||
<!-- Drawable that replaces the up indicator -->
|
||||
<attr name="mdSlideDrawable" format="reference" />
|
||||
|
||||
<!-- String to use as the up indicators content description when the drawer is open -->
|
||||
<attr name="mdDrawerOpenUpContentDescription" format="string" />
|
||||
|
||||
<!-- String to use as the up indicators content description when the drawer is closed -->
|
||||
<attr name="mdDrawerClosedUpContentDescription" format="string" />
|
||||
|
||||
<!-- Whether an overlay should be drawn as the drawer is opened and closed -->
|
||||
<attr name="mdDrawOverlay" format="boolean" />
|
||||
|
||||
<!-- The position of the drawer -->
|
||||
<attr name="mdPosition" format="enum">
|
||||
<enum name="left" value="0" />
|
||||
<enum name="top" value="1" />
|
||||
<enum name="right" value="2" />
|
||||
<enum name="bottom" value="3" />
|
||||
<enum name="start" value="4" />
|
||||
<enum name="end" value="5" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
@ -1,6 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!-- The default background of the menu. -->
|
||||
<color name="md__defaultBackground">#FF555555</color>
|
||||
|
||||
</resources>
|
@ -1,24 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<!-- ID used when defining the content layout in XML. -->
|
||||
<item name="mdContent" type="id" />
|
||||
|
||||
<!-- ID used when defining the menu layout in XML. -->
|
||||
<item name="mdMenu" type="id" />
|
||||
|
||||
<!-- The ID of the content container. -->
|
||||
<item name="md__content" type="id" />
|
||||
|
||||
<!-- The ID of the menu container. -->
|
||||
<item name="md__menu" type="id" />
|
||||
|
||||
<!-- The ID of the drawer. -->
|
||||
<item name="md__drawer" type="id" />
|
||||
|
||||
<!-- Used with View#setTag(int) to specify a position for the active view. -->
|
||||
<item name="mdActiveViewPosition" type="id" />
|
||||
|
||||
<item name="md__translationX" type="id" />
|
||||
|
||||
<item name="md__translationY" type="id" />
|
||||
</resources>
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string name="md__drawerOpenIndicatorDesc" tools:ignore="MissingTranslation">Close drawer</string>
|
||||
|
||||
<string name="md__drawerClosedIndicatorDesc" tools:ignore="MissingTranslation">Open drawer</string>
|
||||
|
||||
</resources>
|
@ -1,13 +0,0 @@
|
||||
<resources>
|
||||
|
||||
<style name="Widget" />
|
||||
|
||||
<!-- Base theme for the menu drawer. -->
|
||||
<style name="Widget.MenuDrawer">
|
||||
<item name="mdMenuBackground">@color/md__defaultBackground</item>
|
||||
<item name="mdContentBackground">?android:attr/windowBackground</item>
|
||||
<item name="mdDrawerOpenUpContentDescription">@string/md__drawerOpenIndicatorDesc</item>
|
||||
<item name="mdDrawerClosedUpContentDescription">@string/md__drawerClosedIndicatorDesc</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -4,11 +4,13 @@ ext.versions = [
|
||||
compileSdk : 29,
|
||||
gradle : '6.5',
|
||||
|
||||
navigation : "2.3.2",
|
||||
androidTools : "4.0.0",
|
||||
ktlint : "0.37.1",
|
||||
ktlintGradle : "9.2.1",
|
||||
detekt : "1.0.0.RC6-4",
|
||||
jacoco : "0.8.5",
|
||||
preferences : "1.1.1",
|
||||
|
||||
androidSupport : "28.0.0",
|
||||
androidLegacySupport : "1.0.0",
|
||||
@ -25,7 +27,7 @@ ext.versions = [
|
||||
okhttp : "3.10.0",
|
||||
semver : "1.0.0",
|
||||
twitterSerial : "0.1.6",
|
||||
koin : "2.1.6",
|
||||
koin : "2.2.2",
|
||||
picasso : "2.71828",
|
||||
|
||||
junit4 : "4.12",
|
||||
@ -42,23 +44,29 @@ ext.versions = [
|
||||
]
|
||||
|
||||
ext.gradlePlugins = [
|
||||
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
|
||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
||||
ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
||||
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
||||
jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco"
|
||||
androidTools : "com.android.tools.build:gradle:$versions.androidTools",
|
||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
||||
ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
||||
detekt : "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
||||
jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco",
|
||||
]
|
||||
|
||||
ext.androidSupport = [
|
||||
support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport",
|
||||
design : "com.google.android.material:material:$versions.androidSupportDesign",
|
||||
annotations : "com.android.support:support-annotations:$versions.androidSupport",
|
||||
multidex : "androidx.multidex:multidex:$versions.multidex",
|
||||
constraintLayout : "androidx.constraintlayout:constraintlayout:$versions.constraintLayout",
|
||||
room : "androidx.room:room-compiler:$versions.room",
|
||||
roomRuntime : "androidx.room:room-runtime:$versions.room",
|
||||
roomKtx : "androidx.room:room-ktx:$versions.room",
|
||||
viewModelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.viewModelKtx"
|
||||
support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport",
|
||||
design : "com.google.android.material:material:$versions.androidSupportDesign",
|
||||
annotations : "com.android.support:support-annotations:$versions.androidSupport",
|
||||
multidex : "androidx.multidex:multidex:$versions.multidex",
|
||||
constraintLayout : "androidx.constraintlayout:constraintlayout:$versions.constraintLayout",
|
||||
room : "androidx.room:room-compiler:$versions.room",
|
||||
roomRuntime : "androidx.room:room-runtime:$versions.room",
|
||||
roomKtx : "androidx.room:room-ktx:$versions.room",
|
||||
viewModelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.viewModelKtx",
|
||||
navigationFragment : "androidx.navigation:navigation-fragment:$versions.navigation",
|
||||
navigationUi : "androidx.navigation:navigation-ui:$versions.navigation",
|
||||
navigationFragmentKtx : "androidx.navigation:navigation-fragment-ktx:$versions.navigation",
|
||||
navigationUiKtx : "androidx.navigation:navigation-ui-ktx:$versions.navigation",
|
||||
navigationFeature : "androidx.navigation:navigation-dynamic-features-fragment:$versions.navigation",
|
||||
preferences : "androidx.preference:preference:$versions.preferences",
|
||||
]
|
||||
|
||||
ext.other = [
|
||||
|
@ -3,5 +3,4 @@ include ':core:domain'
|
||||
include ':core:subsonic-api'
|
||||
include ':core:subsonic-api-image-loader'
|
||||
include ':core:cache'
|
||||
include ':core:menudrawer'
|
||||
include ':ultrasonic'
|
||||
|
@ -50,6 +50,10 @@ android {
|
||||
warning 'MissingTranslation'
|
||||
abortOnError true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
@ -57,7 +61,6 @@ tasks.withType(Test) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':core:menudrawer')
|
||||
implementation project(':core:library')
|
||||
implementation project(':core:domain')
|
||||
implementation project(':core:subsonic-api')
|
||||
@ -71,6 +74,13 @@ dependencies {
|
||||
implementation androidSupport.roomKtx
|
||||
implementation androidSupport.viewModelKtx
|
||||
implementation androidSupport.constraintLayout
|
||||
implementation androidSupport.preferences
|
||||
|
||||
implementation androidSupport.navigationFragment
|
||||
implementation androidSupport.navigationUi
|
||||
implementation androidSupport.navigationFragmentKtx
|
||||
implementation androidSupport.navigationUiKtx
|
||||
implementation androidSupport.navigationFeature
|
||||
|
||||
implementation other.kotlinStdlib
|
||||
implementation other.kotlinxCoroutines
|
||||
|
@ -23,108 +23,31 @@
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:theme="@style/NoActionBar"
|
||||
android:name=".app.UApp"
|
||||
android:label="@string/common.appname"
|
||||
android:usesCleartextTraffic="true">
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
<activity android:name=".activity.NavigationActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/common.appname"
|
||||
android:launchMode="standard">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.SelectArtistActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="standard"/>
|
||||
<activity
|
||||
android:name=".activity.SelectAlbumActivity"
|
||||
android:configChanges="orientation|keyboardHidden"/>
|
||||
<activity
|
||||
android:name=".activity.SearchActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/search.label"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".activity.SelectPlaylistActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/playlist.label"
|
||||
android:launchMode="standard"/>
|
||||
<activity
|
||||
android:name=".activity.PodcastsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/podcasts.label"
|
||||
android:launchMode="standard"/>
|
||||
<activity
|
||||
android:name=".activity.BookmarkActivity"
|
||||
android:configChanges="orientation|keyboardHidden"/>
|
||||
<activity
|
||||
android:name=".activity.ShareActivity"
|
||||
android:configChanges="orientation|keyboardHidden"/>
|
||||
<activity
|
||||
android:name=".activity.ChatActivity"
|
||||
android:configChanges="orientation|keyboardHidden"/>
|
||||
<activity
|
||||
android:name=".activity.DownloadActivity"
|
||||
android:configChanges="keyboardHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true" />
|
||||
<activity
|
||||
android:name=".activity.SettingsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".activity.HelpActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".activity.LyricsActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".activity.EqualizerActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/equalizer.label"
|
||||
android:launchMode="singleTask"/>
|
||||
<activity
|
||||
android:name=".activity.SelectGenreActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="standard"/>
|
||||
<activity
|
||||
android:name=".activity.VoiceQueryReceiverActivity"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.QueryReceiverActivity"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.SEARCH"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<nav-graph android:value="@navigation/navigation_graph" />
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activity.ServerSelectorActivity"
|
||||
android:label="@string/server_selector.label" />
|
||||
<activity
|
||||
android:name=".activity.EditServerActivity"
|
||||
android:label="@string/server_editor.label" />
|
||||
|
||||
<service
|
||||
android:name=".service.MediaPlayerService"
|
||||
android:label="Ultrasonic Download Service"
|
||||
android:label="Ultrasonic Media Player Service"
|
||||
android:exported="false">
|
||||
</service>
|
||||
|
||||
@ -202,10 +125,6 @@
|
||||
android:name=".provider.SearchSuggestionProvider"
|
||||
android:authorities="org.moire.ultrasonic.provider.SearchSuggestionProvider"/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="org.moire.ultrasonic.activity.QueryReceiverActivity"/>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.A2dpIntentReceiver"
|
||||
android:exported="false">
|
||||
|
@ -1,485 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
import org.moire.ultrasonic.service.DownloadFile;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Pair;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.EntryAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BookmarkActivity extends SubsonicTabActivity
|
||||
{
|
||||
|
||||
private SwipeRefreshLayout refreshAlbumListView;
|
||||
private ListView albumListView;
|
||||
private View albumButtons;
|
||||
private View emptyView;
|
||||
private ImageView playNowButton;
|
||||
private ImageView pinButton;
|
||||
private ImageView unpinButton;
|
||||
private ImageView downloadButton;
|
||||
private ImageView deleteButton;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.select_album);
|
||||
|
||||
albumButtons = findViewById(R.id.menu_album);
|
||||
|
||||
refreshAlbumListView = findViewById(R.id.select_album_entries_refresh);
|
||||
albumListView = findViewById(R.id.select_album_entries_list);
|
||||
|
||||
refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
});
|
||||
|
||||
albumListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
|
||||
albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (position >= 0)
|
||||
{
|
||||
Entry entry = (Entry) parent.getItemAtPosition(position);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
if (entry.isVideo())
|
||||
{
|
||||
playVideo(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
enableButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ImageView selectButton = (ImageView) findViewById(R.id.select_album_select);
|
||||
playNowButton = (ImageView) findViewById(R.id.select_album_play_now);
|
||||
ImageView playNextButton = (ImageView) findViewById(R.id.select_album_play_next);
|
||||
ImageView playLastButton = (ImageView) findViewById(R.id.select_album_play_last);
|
||||
pinButton = (ImageView) findViewById(R.id.select_album_pin);
|
||||
unpinButton = (ImageView) findViewById(R.id.select_album_unpin);
|
||||
downloadButton = (ImageView) findViewById(R.id.select_album_download);
|
||||
deleteButton = (ImageView) findViewById(R.id.select_album_delete);
|
||||
ImageView oreButton = (ImageView) findViewById(R.id.select_album_more);
|
||||
emptyView = findViewById(R.id.select_album_empty);
|
||||
|
||||
selectButton.setVisibility(View.GONE);
|
||||
playNextButton.setVisibility(View.GONE);
|
||||
playLastButton.setVisibility(View.GONE);
|
||||
oreButton.setVisibility(View.GONE);
|
||||
|
||||
playNowButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
playNow(getSelectedSongs(albumListView));
|
||||
}
|
||||
});
|
||||
|
||||
selectButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
selectAllOrNone();
|
||||
}
|
||||
});
|
||||
pinButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
downloadBackground(true);
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
unpinButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
unpin();
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
downloadButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
downloadBackground(false);
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
deleteButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
delete();
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(albumListView);
|
||||
|
||||
enableButtons();
|
||||
|
||||
View browseMenuItem = findViewById(R.id.menu_bookmarks);
|
||||
menuDrawer.setActiveView(browseMenuItem);
|
||||
|
||||
getBookmarks();
|
||||
}
|
||||
|
||||
private void getBookmarks()
|
||||
{
|
||||
setActionBarSubtitle(R.string.button_bar_bookmarks);
|
||||
|
||||
new LoadTask()
|
||||
{
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception
|
||||
{
|
||||
return Util.getSongsFromBookmarks(service.getBookmarks(BookmarkActivity.this, this));
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void playNow(List<Entry> songs)
|
||||
{
|
||||
if (!getSelectedSongs(albumListView).isEmpty())
|
||||
{
|
||||
int position = songs.get(0).getBookmarkPosition();
|
||||
if (getMediaPlayerController() == null) return;
|
||||
getMediaPlayerController().restore(songs, 0, position, true, true);
|
||||
selectAll(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<MusicDirectory.Entry> getSelectedSongs(ListView albumListView)
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>(10);
|
||||
|
||||
if (albumListView != null)
|
||||
{
|
||||
int count = albumListView.getCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (albumListView.isItemChecked(i))
|
||||
{
|
||||
songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
finish();
|
||||
Intent intent = getIntent();
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
menuDrawer.toggleMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void selectAllOrNone()
|
||||
{
|
||||
boolean someUnselected = false;
|
||||
int count = albumListView.getCount();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!albumListView.isItemChecked(i) && albumListView.getItemAtPosition(i) instanceof MusicDirectory.Entry)
|
||||
{
|
||||
someUnselected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
selectAll(someUnselected, true);
|
||||
}
|
||||
|
||||
private void selectAll(boolean selected, boolean toast)
|
||||
{
|
||||
int count = albumListView.getCount();
|
||||
int selectedCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i);
|
||||
if (entry != null && !entry.isDirectory() && !entry.isVideo())
|
||||
{
|
||||
albumListView.setItemChecked(i, selected);
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Display toast: N tracks selected / N tracks unselected
|
||||
if (toast)
|
||||
{
|
||||
int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected;
|
||||
Util.toast(this, getString(toastResId, selectedCount));
|
||||
}
|
||||
|
||||
enableButtons();
|
||||
}
|
||||
|
||||
private void enableButtons()
|
||||
{
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
if (mediaPlayerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<MusicDirectory.Entry> selection = getSelectedSongs(albumListView);
|
||||
boolean enabled = !selection.isEmpty();
|
||||
boolean unpinEnabled = false;
|
||||
boolean deleteEnabled = false;
|
||||
|
||||
int pinnedCount = 0;
|
||||
|
||||
for (MusicDirectory.Entry song : selection)
|
||||
{
|
||||
DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song);
|
||||
if (downloadFile.isWorkDone())
|
||||
{
|
||||
deleteEnabled = true;
|
||||
}
|
||||
|
||||
if (downloadFile.isSaved())
|
||||
{
|
||||
pinnedCount++;
|
||||
unpinEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
playNowButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE);
|
||||
pinButton.setVisibility((enabled && !ActiveServerProvider.Companion.isOffline(this) && selection.size() > pinnedCount) ? View.VISIBLE : View.GONE);
|
||||
unpinButton.setVisibility(enabled && unpinEnabled ? View.VISIBLE : View.GONE);
|
||||
downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(this) ? View.VISIBLE : View.GONE);
|
||||
deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void downloadBackground(final boolean save)
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs(albumListView);
|
||||
|
||||
if (songs.isEmpty())
|
||||
{
|
||||
selectAll(true, false);
|
||||
songs = getSelectedSongs(albumListView);
|
||||
}
|
||||
|
||||
downloadBackground(save, songs);
|
||||
}
|
||||
|
||||
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
if (getMediaPlayerController() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable onValid = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
getMediaPlayerController().downloadBackground(songs, save);
|
||||
|
||||
if (save)
|
||||
{
|
||||
Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkLicenseAndTrialPeriod(onValid);
|
||||
}
|
||||
|
||||
private void delete()
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs(albumListView);
|
||||
|
||||
if (songs.isEmpty())
|
||||
{
|
||||
selectAll(true, false);
|
||||
songs = getSelectedSongs(albumListView);
|
||||
}
|
||||
|
||||
if (getMediaPlayerController() != null)
|
||||
{
|
||||
getMediaPlayerController().delete(songs);
|
||||
}
|
||||
}
|
||||
|
||||
private void unpin()
|
||||
{
|
||||
if (getMediaPlayerController() != null)
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs(albumListView);
|
||||
Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size()));
|
||||
getMediaPlayerController().unpin(songs);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class LoadTask extends TabActivityBackgroundTask<Pair<MusicDirectory, Boolean>>
|
||||
{
|
||||
|
||||
public LoadTask()
|
||||
{
|
||||
super(BookmarkActivity.this, true);
|
||||
}
|
||||
|
||||
protected abstract MusicDirectory load(MusicService service) throws Exception;
|
||||
|
||||
@Override
|
||||
protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(BookmarkActivity.this);
|
||||
MusicDirectory dir = load(musicService);
|
||||
boolean valid = musicService.isLicenseValid(BookmarkActivity.this, this);
|
||||
return new Pair<MusicDirectory, Boolean>(dir, valid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Pair<MusicDirectory, Boolean> result)
|
||||
{
|
||||
MusicDirectory musicDirectory = result.getFirst();
|
||||
List<MusicDirectory.Entry> entries = musicDirectory.getChildren();
|
||||
|
||||
int songCount = 0;
|
||||
for (MusicDirectory.Entry entry : entries)
|
||||
{
|
||||
if (!entry.isDirectory())
|
||||
{
|
||||
songCount++;
|
||||
}
|
||||
}
|
||||
|
||||
final int listSize = getIntent().getIntExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0);
|
||||
|
||||
if (songCount > 0)
|
||||
{
|
||||
pinButton.setVisibility(View.VISIBLE);
|
||||
unpinButton.setVisibility(View.VISIBLE);
|
||||
downloadButton.setVisibility(View.VISIBLE);
|
||||
deleteButton.setVisibility(View.VISIBLE);
|
||||
playNowButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
pinButton.setVisibility(View.GONE);
|
||||
unpinButton.setVisibility(View.GONE);
|
||||
downloadButton.setVisibility(View.GONE);
|
||||
deleteButton.setVisibility(View.GONE);
|
||||
playNowButton.setVisibility(View.GONE);
|
||||
|
||||
if (listSize == 0 || result.getFirst().getChildren().size() < listSize)
|
||||
{
|
||||
albumButtons.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
enableButtons();
|
||||
|
||||
emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
albumListView.setAdapter(new EntryAdapter(BookmarkActivity.this, getImageLoader(), entries, true));
|
||||
licenseValid = result.getSecond();
|
||||
}
|
||||
}
|
||||
|
||||
private class GetDataTask extends AsyncTask<Void, Void, String[]>
|
||||
{
|
||||
@Override
|
||||
protected void onPostExecute(String[] result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] doInBackground(Void... params)
|
||||
{
|
||||
refresh();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,310 +0,0 @@
|
||||
package org.moire.ultrasonic.activity;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.ChatMessage;
|
||||
import org.moire.ultrasonic.service.JukeboxMediaPlayer;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.ChatAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* @author Joshua Bahnsen
|
||||
*/
|
||||
public final class ChatActivity extends SubsonicTabActivity
|
||||
{
|
||||
private ListView chatListView;
|
||||
private EditText messageEditText;
|
||||
private ImageButton sendButton;
|
||||
private Timer timer;
|
||||
private volatile static Long lastChatMessageTime = (long) 0;
|
||||
private volatile static ArrayList<ChatMessage> messageList = new ArrayList<ChatMessage>();
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle)
|
||||
{
|
||||
super.onCreate(bundle);
|
||||
setContentView(R.layout.chat);
|
||||
|
||||
messageEditText = (EditText) findViewById(R.id.chat_edittext);
|
||||
sendButton = (ImageButton) findViewById(R.id.chat_send);
|
||||
|
||||
sendButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
chatListView = findViewById(R.id.chat_entries_list);
|
||||
chatListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
|
||||
chatListView.setStackFromBottom(true);
|
||||
|
||||
String serverName = activeServerProvider.getValue().getActiveServer().getName();
|
||||
String userName = activeServerProvider.getValue().getActiveServer().getUserName();
|
||||
String title = String.format("%s [%s@%s]", getResources().getString(R.string.button_bar_chat), userName, serverName);
|
||||
|
||||
setActionBarSubtitle(title);
|
||||
|
||||
messageEditText.setImeActionLabel("Send", KeyEvent.KEYCODE_ENTER);
|
||||
|
||||
messageEditText.addTextChangedListener(new TextWatcher()
|
||||
{
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable)
|
||||
{
|
||||
sendButton.setEnabled(!Util.isNullOrWhiteSpace(editable.toString()));
|
||||
}
|
||||
});
|
||||
|
||||
messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
|
||||
{
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
{
|
||||
sendMessage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
View chatMenuItem = findViewById(R.id.menu_chat);
|
||||
menuDrawer.setActiveView(chatMenuItem);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle bundle)
|
||||
{
|
||||
super.onPostCreate(bundle);
|
||||
|
||||
timerMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.chat, menu);
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Listen for option item selections so that we receive a notification
|
||||
* when the user requests a refresh by selecting the refresh action bar item.
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
// Check if user triggered a refresh:
|
||||
case R.id.menu_refresh:
|
||||
// Start the refresh background task.
|
||||
new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return true;
|
||||
}
|
||||
// User didn't trigger a refresh, let the superclass handle this action
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (!messageList.isEmpty())
|
||||
{
|
||||
ListAdapter chatAdapter = new ChatAdapter(ChatActivity.this, messageList);
|
||||
chatListView.setAdapter(chatAdapter);
|
||||
}
|
||||
|
||||
if (timer == null)
|
||||
{
|
||||
timerMethod();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
|
||||
if (timer != null)
|
||||
{
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void timerMethod()
|
||||
{
|
||||
int refreshInterval = Util.getChatRefreshInterval(this);
|
||||
|
||||
if (refreshInterval > 0)
|
||||
{
|
||||
timer = new Timer();
|
||||
|
||||
timer.schedule(new TimerTask()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
ChatActivity.this.runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
load();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, refreshInterval, refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage()
|
||||
{
|
||||
if (messageEditText != null)
|
||||
{
|
||||
final String message;
|
||||
Editable text = messageEditText.getText();
|
||||
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
message = text.toString();
|
||||
|
||||
if (!Util.isNullOrWhiteSpace(message))
|
||||
{
|
||||
messageEditText.setText("");
|
||||
|
||||
BackgroundTask<Void> task = new TabActivityBackgroundTask<Void>(ChatActivity.this, false)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(ChatActivity.this);
|
||||
musicService.addChatMessage(message, ChatActivity.this, this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
load();
|
||||
}
|
||||
};
|
||||
|
||||
task.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void load()
|
||||
{
|
||||
BackgroundTask<List<ChatMessage>> task = new TabActivityBackgroundTask<List<ChatMessage>>(this, false)
|
||||
{
|
||||
@Override
|
||||
protected List<ChatMessage> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(ChatActivity.this);
|
||||
return musicService.getChatMessages(lastChatMessageTime, ChatActivity.this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<ChatMessage> result)
|
||||
{
|
||||
if (result != null && !result.isEmpty())
|
||||
{
|
||||
// Reset lastChatMessageTime if we have a newer message
|
||||
for (ChatMessage message : result)
|
||||
{
|
||||
if (message.getTime() > lastChatMessageTime)
|
||||
{
|
||||
lastChatMessageTime = message.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse results to show them on the bottom
|
||||
Collections.reverse(result);
|
||||
messageList.addAll(result);
|
||||
|
||||
ListAdapter chatAdapter = new ChatAdapter(ChatActivity.this, messageList);
|
||||
chatListView.setAdapter(chatAdapter);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
task.execute();
|
||||
}
|
||||
|
||||
private class GetDataTask extends AsyncTask<Void, Void, String[]>
|
||||
{
|
||||
@Override
|
||||
protected void onPostExecute(String[] result)
|
||||
{
|
||||
load();
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] doInBackground(Void... params)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,280 +0,0 @@
|
||||
/*
|
||||
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 2011 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.activity;
|
||||
|
||||
import android.media.audiofx.Equalizer;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.audiofx.EqualizerController;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Equalizer controls.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public class EqualizerActivity extends ResultActivity
|
||||
{
|
||||
private static final int MENU_GROUP_PRESET = 100;
|
||||
|
||||
private final Map<Short, SeekBar> bars = new HashMap<Short, SeekBar>();
|
||||
private EqualizerController equalizerController;
|
||||
private Equalizer equalizer;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle)
|
||||
{
|
||||
super.onCreate(bundle);
|
||||
setContentView(R.layout.equalizer);
|
||||
|
||||
EqualizerController.get().observe(this, new Observer<EqualizerController>() {
|
||||
@Override
|
||||
public void onChanged(EqualizerController controller) {
|
||||
if (controller != null) {
|
||||
Timber.d("EqualizerController Observer.onChanged received controller");
|
||||
equalizerController = controller;
|
||||
equalizer = controller.equalizer;
|
||||
setup();
|
||||
} else {
|
||||
Timber.d("EqualizerController Observer.onChanged has no controller");
|
||||
equalizerController = null;
|
||||
equalizer = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
if (equalizerController == null) return;
|
||||
equalizerController.saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
if (equalizer == null) return;
|
||||
|
||||
short currentPreset;
|
||||
try
|
||||
{
|
||||
currentPreset = equalizer.getCurrentPreset();
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
currentPreset = -1;
|
||||
}
|
||||
|
||||
for (short preset = 0; preset < equalizer.getNumberOfPresets(); preset++)
|
||||
{
|
||||
MenuItem menuItem = menu.add(MENU_GROUP_PRESET, preset, preset, equalizer.getPresetName(preset));
|
||||
if (preset == currentPreset)
|
||||
{
|
||||
menuItem.setChecked(true);
|
||||
}
|
||||
}
|
||||
menu.setGroupCheckable(MENU_GROUP_PRESET, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
if (equalizer == null) return true;
|
||||
try
|
||||
{
|
||||
short preset = (short) menuItem.getItemId();
|
||||
equalizer.usePreset(preset);
|
||||
updateBars();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setup()
|
||||
{
|
||||
initEqualizer();
|
||||
|
||||
final View presetButton = findViewById(R.id.equalizer_preset);
|
||||
registerForContextMenu(presetButton);
|
||||
presetButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
presetButton.showContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
CheckBox enabledCheckBox = (CheckBox) findViewById(R.id.equalizer_enabled);
|
||||
enabledCheckBox.setChecked(equalizer.getEnabled());
|
||||
enabledCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
|
||||
{
|
||||
setEqualizerEnabled(b);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setEqualizerEnabled(boolean enabled)
|
||||
{
|
||||
if (equalizer == null) return;
|
||||
equalizer.setEnabled(enabled);
|
||||
updateBars();
|
||||
}
|
||||
|
||||
private void updateBars()
|
||||
{
|
||||
if (equalizer == null) return;
|
||||
try
|
||||
{
|
||||
for (Map.Entry<Short, SeekBar> entry : bars.entrySet())
|
||||
{
|
||||
short band = entry.getKey();
|
||||
SeekBar bar = entry.getValue();
|
||||
bar.setEnabled(equalizer.getEnabled());
|
||||
short minEQLevel = equalizer.getBandLevelRange()[0];
|
||||
bar.setProgress(equalizer.getBandLevel(band) - minEQLevel);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog
|
||||
}
|
||||
}
|
||||
|
||||
private void initEqualizer()
|
||||
{
|
||||
if (equalizer == null) return;
|
||||
LinearLayout layout = (LinearLayout) findViewById(R.id.equalizer_layout);
|
||||
|
||||
try
|
||||
{
|
||||
short[] bandLevelRange = equalizer.getBandLevelRange();
|
||||
short numberOfBands = equalizer.getNumberOfBands();
|
||||
|
||||
final short minEQLevel = bandLevelRange[0];
|
||||
final short maxEQLevel = bandLevelRange[1];
|
||||
|
||||
for (short i = 0; i < numberOfBands; i++)
|
||||
{
|
||||
final short band = i;
|
||||
|
||||
View bandBar = LayoutInflater.from(this).inflate(R.layout.equalizer_bar, null);
|
||||
TextView freqTextView;
|
||||
|
||||
if (bandBar != null)
|
||||
{
|
||||
freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency);
|
||||
final TextView levelTextView = (TextView) bandBar.findViewById(R.id.equalizer_level);
|
||||
SeekBar bar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar);
|
||||
|
||||
freqTextView.setText((equalizer.getCenterFreq(band) / 1000) + " Hz");
|
||||
|
||||
bars.put(band, bar);
|
||||
bar.setMax(maxEQLevel - minEQLevel);
|
||||
short level = equalizer.getBandLevel(band);
|
||||
bar.setProgress(level - minEQLevel);
|
||||
bar.setEnabled(equalizer.getEnabled());
|
||||
updateLevelText(levelTextView, level);
|
||||
|
||||
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
||||
{
|
||||
short level = (short) (progress + minEQLevel);
|
||||
if (fromUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
equalizer.setBandLevel(band, level);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog
|
||||
}
|
||||
}
|
||||
updateLevelText(levelTextView, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
layout.addView(bandBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateLevelText(TextView levelTextView, short level)
|
||||
{
|
||||
if (levelTextView != null)
|
||||
{
|
||||
levelTextView.setText(String.format("%s%d dB", level > 0 ? "+" : "", level / 100));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,325 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.Window;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import net.simonvt.menudrawer.MenuDrawer;
|
||||
import net.simonvt.menudrawer.Position;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* An HTML-based help screen with Back and Done buttons at the bottom.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public final class HelpActivity extends ResultActivity implements OnClickListener
|
||||
{
|
||||
private WebView webView;
|
||||
private ImageView backButton;
|
||||
private ImageView forwardButton;
|
||||
|
||||
private static final String STATE_MENUDRAWER = "org.moire.ultrasonic.menuDrawer";
|
||||
private static final String STATE_ACTIVE_VIEW_ID = "org.moire.ultrasonic.activeViewId";
|
||||
private static final String STATE_ACTIVE_POSITION = "org.moire.ultrasonic.activePosition";
|
||||
|
||||
public MenuDrawer menuDrawer;
|
||||
private int activePosition = 1;
|
||||
private int menuActiveViewId;
|
||||
View chatMenuItem;
|
||||
View bookmarksMenuItem;
|
||||
View sharesMenuItem;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle)
|
||||
{
|
||||
Util.applyTheme(this);
|
||||
getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
super.onCreate(bundle);
|
||||
|
||||
setContentView(R.layout.help);
|
||||
|
||||
if (bundle != null)
|
||||
{
|
||||
activePosition = bundle.getInt(STATE_ACTIVE_POSITION);
|
||||
menuActiveViewId = bundle.getInt(STATE_ACTIVE_VIEW_ID);
|
||||
}
|
||||
|
||||
menuDrawer = MenuDrawer.attach(this, MenuDrawer.Type.BEHIND, Position.LEFT, MenuDrawer.MENU_DRAG_WINDOW);
|
||||
menuDrawer.setMenuView(R.layout.menu_main);
|
||||
|
||||
chatMenuItem = findViewById(R.id.menu_chat);
|
||||
bookmarksMenuItem = findViewById(R.id.menu_bookmarks);
|
||||
sharesMenuItem = findViewById(R.id.menu_shares);
|
||||
View aboutMenuItem = findViewById(R.id.menu_about);
|
||||
|
||||
findViewById(R.id.menu_home).setOnClickListener(this);
|
||||
findViewById(R.id.menu_browse).setOnClickListener(this);
|
||||
findViewById(R.id.menu_search).setOnClickListener(this);
|
||||
findViewById(R.id.menu_playlists).setOnClickListener(this);
|
||||
sharesMenuItem.setOnClickListener(this);
|
||||
chatMenuItem.setOnClickListener(this);
|
||||
bookmarksMenuItem.setOnClickListener(this);
|
||||
findViewById(R.id.menu_now_playing).setOnClickListener(this);
|
||||
findViewById(R.id.menu_settings).setOnClickListener(this);
|
||||
aboutMenuItem.setOnClickListener(this);
|
||||
findViewById(R.id.menu_exit).setOnClickListener(this);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
|
||||
if (actionBar != null)
|
||||
{
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
menuDrawer.setActiveView(aboutMenuItem);
|
||||
|
||||
webView = (WebView) findViewById(R.id.help_contents);
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.setWebViewClient(new HelpClient());
|
||||
|
||||
if (bundle != null)
|
||||
{
|
||||
webView.restoreState(bundle);
|
||||
}
|
||||
else
|
||||
{
|
||||
webView.loadUrl(getResources().getString(R.string.help_url));
|
||||
}
|
||||
|
||||
backButton = (ImageView) findViewById(R.id.help_back);
|
||||
backButton.setOnClickListener(new Button.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
webView.goBack();
|
||||
}
|
||||
});
|
||||
|
||||
ImageView stopButton = (ImageView) findViewById(R.id.help_stop);
|
||||
stopButton.setOnClickListener(new Button.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
webView.stopLoading();
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
}
|
||||
});
|
||||
|
||||
forwardButton = (ImageView) findViewById(R.id.help_forward);
|
||||
forwardButton.setOnClickListener(new Button.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
webView.goForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle bundle)
|
||||
{
|
||||
super.onPostCreate(bundle);
|
||||
|
||||
int visibility = ActiveServerProvider.Companion.isOffline(this) ? View.GONE : View.VISIBLE;
|
||||
chatMenuItem.setVisibility(visibility);
|
||||
bookmarksMenuItem.setVisibility(visibility);
|
||||
sharesMenuItem.setVisibility(visibility);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle state)
|
||||
{
|
||||
webView.saveState(state);
|
||||
super.onSaveInstanceState(state);
|
||||
state.putParcelable(STATE_MENUDRAWER, menuDrawer.saveState());
|
||||
state.putInt(STATE_ACTIVE_VIEW_ID, menuActiveViewId);
|
||||
state.putInt(STATE_ACTIVE_POSITION, activePosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event)
|
||||
{
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
if (webView.canGoBack())
|
||||
{
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
menuDrawer.toggleMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle state)
|
||||
{
|
||||
super.onRestoreInstanceState(state);
|
||||
menuDrawer.restoreState(state.getParcelable(STATE_MENUDRAWER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed()
|
||||
{
|
||||
final int drawerState = menuDrawer.getDrawerState();
|
||||
|
||||
if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING)
|
||||
{
|
||||
menuDrawer.closeMenu(true);
|
||||
return;
|
||||
}
|
||||
|
||||
finish();
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
menuActiveViewId = v.getId();
|
||||
menuDrawer.setActiveView(v);
|
||||
|
||||
Intent intent;
|
||||
|
||||
switch (menuActiveViewId)
|
||||
{
|
||||
case R.id.menu_home:
|
||||
|
||||
intent = new Intent(this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
break;
|
||||
case R.id.menu_browse:
|
||||
intent = new Intent(this, SelectArtistActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
break;
|
||||
case R.id.menu_search:
|
||||
intent = new Intent(this, SearchActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
break;
|
||||
case R.id.menu_playlists:
|
||||
intent = new Intent(this, SelectPlaylistActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
break;
|
||||
case R.id.menu_shares:
|
||||
intent = new Intent(this, ShareActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
break;
|
||||
case R.id.menu_chat:
|
||||
startActivityForResultWithoutTransition(this, ChatActivity.class);
|
||||
break;
|
||||
case R.id.menu_bookmarks:
|
||||
startActivityForResultWithoutTransition(this, BookmarkActivity.class);
|
||||
break;
|
||||
case R.id.menu_now_playing:
|
||||
startActivityForResultWithoutTransition(this, DownloadActivity.class);
|
||||
break;
|
||||
case R.id.menu_settings:
|
||||
startActivityForResultWithoutTransition(this, SettingsActivity.class);
|
||||
break;
|
||||
case R.id.menu_about:
|
||||
startActivityForResultWithoutTransition(this, HelpActivity.class);
|
||||
break;
|
||||
case R.id.menu_exit:
|
||||
intent = new Intent(this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
break;
|
||||
}
|
||||
|
||||
menuDrawer.closeMenu(true);
|
||||
}
|
||||
|
||||
private final class HelpClient extends WebViewClient
|
||||
{
|
||||
@Override
|
||||
public void onLoadResource(WebView webView, String url)
|
||||
{
|
||||
setProgressBarIndeterminateVisibility(true);
|
||||
super.onLoadResource(webView, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url)
|
||||
{
|
||||
setProgressBarIndeterminateVisibility(false);
|
||||
String versionName = Util.getVersionName(HelpActivity.this);
|
||||
String title = String.format("%s (%s)", view.getTitle(), versionName);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
|
||||
if (actionBar != null)
|
||||
{
|
||||
actionBar.setSubtitle(title);
|
||||
}
|
||||
|
||||
backButton.setEnabled(view.canGoBack());
|
||||
forwardButton.setEnabled(view.canGoForward());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
|
||||
{
|
||||
Util.toast(HelpActivity.this, description);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.Lyrics;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
|
||||
/**
|
||||
* Displays song lyrics.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public final class LyricsActivity extends SubsonicTabActivity
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle)
|
||||
{
|
||||
super.onCreate(bundle);
|
||||
setContentView(R.layout.lyrics);
|
||||
|
||||
View nowPlayingMenuItem = findViewById(R.id.menu_now_playing);
|
||||
menuDrawer.setActiveView(nowPlayingMenuItem);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
BackgroundTask<Lyrics> task = new TabActivityBackgroundTask<Lyrics>(this, true)
|
||||
{
|
||||
@Override
|
||||
protected Lyrics doInBackground() throws Throwable
|
||||
{
|
||||
String artist = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ARTIST);
|
||||
String title = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_TITLE);
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(LyricsActivity.this);
|
||||
return musicService.getLyrics(artist, title, LyricsActivity.this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Lyrics result)
|
||||
{
|
||||
TextView artistView = (TextView) findViewById(R.id.lyrics_artist);
|
||||
TextView titleView = (TextView) findViewById(R.id.lyrics_title);
|
||||
TextView textView = (TextView) findViewById(R.id.lyrics_text);
|
||||
|
||||
if (result != null && result.getArtist() != null)
|
||||
{
|
||||
artistView.setText(result.getArtist());
|
||||
titleView.setText(result.getTitle());
|
||||
textView.setText(result.getText());
|
||||
}
|
||||
else
|
||||
{
|
||||
artistView.setText(R.string.lyrics_nomatch);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
}
|
@ -1,382 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.data.ServerSetting;
|
||||
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.MergeAdapter;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.koin.android.viewmodel.compat.ViewModelCompat.viewModel;
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
public class MainActivity extends SubsonicTabActivity
|
||||
{
|
||||
private static boolean infoDialogDisplayed;
|
||||
private static boolean shouldUseId3;
|
||||
private static String lastActiveServerProperties;
|
||||
|
||||
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
private Lazy<ServerSettingsModel> serverSettingsModel = viewModel(this, ServerSettingsModel.class);
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Determine first run and migrate server settings to DB as early as possible
|
||||
boolean showWelcomeScreen = Util.isFirstRun(this);
|
||||
boolean areServersMigrated = serverSettingsModel.getValue().migrateFromPreferences();
|
||||
|
||||
// If there are any servers in the DB, do not show the welcome screen
|
||||
showWelcomeScreen &= !areServersMigrated;
|
||||
|
||||
if (getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_EXIT))
|
||||
{
|
||||
setResult(Constants.RESULT_CLOSE_ALL);
|
||||
|
||||
if (getMediaPlayerController() != null)
|
||||
{
|
||||
getMediaPlayerController().stopJukeboxService();
|
||||
}
|
||||
|
||||
if (getImageLoader() != null)
|
||||
{
|
||||
getImageLoader().stopImageLoader();
|
||||
}
|
||||
|
||||
finish();
|
||||
exit();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.main);
|
||||
|
||||
loadSettings();
|
||||
|
||||
final View buttons = LayoutInflater.from(this).inflate(R.layout.main_buttons, null);
|
||||
final View serverButton = buttons.findViewById(R.id.main_select_server);
|
||||
final TextView serverTextView = serverButton.findViewById(R.id.main_select_server_2);
|
||||
final View musicTitle = buttons.findViewById(R.id.main_music);
|
||||
final View artistsButton = buttons.findViewById(R.id.main_artists_button);
|
||||
final View albumsButton = buttons.findViewById(R.id.main_albums_button);
|
||||
final View genresButton = buttons.findViewById(R.id.main_genres_button);
|
||||
final View videosTitle = buttons.findViewById(R.id.main_videos_title);
|
||||
final View songsTitle = buttons.findViewById(R.id.main_songs);
|
||||
final View randomSongsButton = buttons.findViewById(R.id.main_songs_button);
|
||||
final View songsStarredButton = buttons.findViewById(R.id.main_songs_starred);
|
||||
final View albumsTitle = buttons.findViewById(R.id.main_albums);
|
||||
final View albumsNewestButton = buttons.findViewById(R.id.main_albums_newest);
|
||||
final View albumsRandomButton = buttons.findViewById(R.id.main_albums_random);
|
||||
final View albumsHighestButton = buttons.findViewById(R.id.main_albums_highest);
|
||||
final View albumsStarredButton = buttons.findViewById(R.id.main_albums_starred);
|
||||
final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent);
|
||||
final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent);
|
||||
final View albumsAlphaByNameButton = buttons.findViewById(R.id.main_albums_alphaByName);
|
||||
final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist);
|
||||
final View videosButton = buttons.findViewById(R.id.main_videos);
|
||||
|
||||
lastActiveServerProperties = getActiveServerProperties();
|
||||
String name = activeServerProvider.getValue().getActiveServer().getName();
|
||||
|
||||
serverTextView.setText(name);
|
||||
|
||||
final ListView list = findViewById(R.id.main_list);
|
||||
|
||||
final MergeAdapter adapter = new MergeAdapter();
|
||||
adapter.addViews(Collections.singletonList(serverButton), true);
|
||||
|
||||
if (!ActiveServerProvider.Companion.isOffline(this))
|
||||
{
|
||||
adapter.addView(musicTitle, false);
|
||||
adapter.addViews(asList(artistsButton, albumsButton, genresButton), true);
|
||||
adapter.addView(songsTitle, false);
|
||||
adapter.addViews(asList(randomSongsButton, songsStarredButton), true);
|
||||
adapter.addView(albumsTitle, false);
|
||||
|
||||
if (Util.getShouldUseId3Tags(MainActivity.this))
|
||||
{
|
||||
shouldUseId3 = true;
|
||||
adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldUseId3 = false;
|
||||
adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsHighestButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true);
|
||||
}
|
||||
|
||||
adapter.addView(videosTitle, false);
|
||||
adapter.addViews(Collections.singletonList(videosButton), true);
|
||||
}
|
||||
|
||||
list.setAdapter(adapter);
|
||||
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (view == serverButton)
|
||||
{
|
||||
showServers();
|
||||
}
|
||||
else if (view == albumsNewestButton)
|
||||
{
|
||||
showAlbumList("newest", R.string.main_albums_newest);
|
||||
}
|
||||
else if (view == albumsRandomButton)
|
||||
{
|
||||
showAlbumList("random", R.string.main_albums_random);
|
||||
}
|
||||
else if (view == albumsHighestButton)
|
||||
{
|
||||
showAlbumList("highest", R.string.main_albums_highest);
|
||||
}
|
||||
else if (view == albumsRecentButton)
|
||||
{
|
||||
showAlbumList("recent", R.string.main_albums_recent);
|
||||
}
|
||||
else if (view == albumsFrequentButton)
|
||||
{
|
||||
showAlbumList("frequent", R.string.main_albums_frequent);
|
||||
}
|
||||
else if (view == albumsStarredButton)
|
||||
{
|
||||
showAlbumList(Constants.STARRED, R.string.main_albums_starred);
|
||||
}
|
||||
else if (view == albumsAlphaByNameButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName);
|
||||
}
|
||||
else if (view == albumsAlphaByArtistButton)
|
||||
{
|
||||
showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist);
|
||||
}
|
||||
else if (view == songsStarredButton)
|
||||
{
|
||||
showStarredSongs();
|
||||
}
|
||||
else if (view == artistsButton)
|
||||
{
|
||||
showArtists();
|
||||
}
|
||||
else if (view == albumsButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title);
|
||||
}
|
||||
else if (view == randomSongsButton)
|
||||
{
|
||||
showRandomSongs();
|
||||
}
|
||||
else if (view == genresButton)
|
||||
{
|
||||
showGenres();
|
||||
}
|
||||
else if (view == videosButton)
|
||||
{
|
||||
showVideos();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final View homeMenuItem = findViewById(R.id.menu_home);
|
||||
menuDrawer.setActiveView(homeMenuItem);
|
||||
|
||||
setActionBarTitle(R.string.common_appname);
|
||||
setTitle(R.string.common_appname);
|
||||
|
||||
// Remember the current theme.
|
||||
theme = Util.getTheme(this);
|
||||
|
||||
showInfoDialog(showWelcomeScreen);
|
||||
}
|
||||
|
||||
private void loadSettings()
|
||||
{
|
||||
PreferenceManager.setDefaultValues(this, R.xml.settings, false);
|
||||
final SharedPreferences preferences = Util.getPreferences(this);
|
||||
|
||||
if (!preferences.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION))
|
||||
{
|
||||
final SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(this).getPath());
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
boolean shouldRestart = false;
|
||||
|
||||
boolean id3 = Util.getShouldUseId3Tags(MainActivity.this);
|
||||
String currentActiveServerProperties = getActiveServerProperties();
|
||||
|
||||
if (id3 != shouldUseId3)
|
||||
{
|
||||
shouldUseId3 = id3;
|
||||
shouldRestart = true;
|
||||
}
|
||||
|
||||
if (!currentActiveServerProperties.equals(lastActiveServerProperties))
|
||||
{
|
||||
lastActiveServerProperties = currentActiveServerProperties;
|
||||
shouldRestart = true;
|
||||
}
|
||||
|
||||
if (shouldRestart) restart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu)
|
||||
{
|
||||
final MenuInflater menuInflater = getMenuInflater();
|
||||
menuInflater.inflate(R.menu.main, menu);
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
menuDrawer.toggleMenu();
|
||||
return true;
|
||||
case R.id.main_shuffle:
|
||||
final Intent intent1 = new Intent(this, DownloadActivity.class);
|
||||
intent1.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||
startActivityForResultWithoutTransition(this, intent1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void exit()
|
||||
{
|
||||
lifecycleSupport.getValue().onDestroy();
|
||||
Util.unregisterMediaButtonEventReceiver(this, false);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void showInfoDialog(final boolean show)
|
||||
{
|
||||
if (!infoDialogDisplayed)
|
||||
{
|
||||
infoDialogDisplayed = true;
|
||||
|
||||
if (show)
|
||||
{
|
||||
Util.showWelcomeDialog(this, this, R.string.main_welcome_title, R.string.main_welcome_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showAlbumList(final String type, final int title)
|
||||
{
|
||||
final Intent intent = new Intent(this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums(this));
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void showStarredSongs()
|
||||
{
|
||||
final Intent intent = new Intent(this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_STARRED, 1);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void showRandomSongs()
|
||||
{
|
||||
final Intent intent = new Intent(this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_RANDOM, 1);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(this));
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void showArtists()
|
||||
{
|
||||
final Intent intent = new Intent(this, SelectArtistActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, getResources().getString(R.string.main_artists_title));
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void showGenres()
|
||||
{
|
||||
final Intent intent = new Intent(this, SelectGenreActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void showVideos()
|
||||
{
|
||||
final Intent intent = new Intent(this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_VIDEOS, 1);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void showServers()
|
||||
{
|
||||
final Intent intent = new Intent(this, ServerSelectorActivity.class);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private String getActiveServerProperties()
|
||||
{
|
||||
ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer();
|
||||
return String.format("%s;%s;%s;%s;%s;%s", currentSetting.getUrl(), currentSetting.getUserName(),
|
||||
currentSetting.getPassword(), currentSetting.getAllowSelfSignedCertificate(),
|
||||
currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion());
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.PodcastsChannel;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.view.PodcastsChannelsAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PodcastsActivity extends SubsonicTabActivity {
|
||||
|
||||
private View emptyTextView;
|
||||
SubsonicTabActivity currentActivity = null;
|
||||
ListView channelItemsListView = null;
|
||||
|
||||
Context currentContext = (Context)this;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
this.currentActivity = this;
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.podcasts);
|
||||
|
||||
|
||||
emptyTextView = findViewById(R.id.select_podcasts_empty);
|
||||
channelItemsListView = (ListView)findViewById(R.id.podcasts_channels_items_list);
|
||||
channelItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
PodcastsChannel pc = (PodcastsChannel) parent.getItemAtPosition(position);
|
||||
if (pc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(currentContext, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID, pc.getId());
|
||||
startActivityForResultWithoutTransition(PodcastsActivity.this, intent);
|
||||
}
|
||||
});
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void load()
|
||||
{
|
||||
BackgroundTask<List<PodcastsChannel>> task = new TabActivityBackgroundTask<List<PodcastsChannel>>(this, true)
|
||||
{
|
||||
@Override
|
||||
protected List<PodcastsChannel> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(PodcastsActivity.this);
|
||||
List<PodcastsChannel> channels = musicService.getPodcastsChannels(false,PodcastsActivity.this, this);
|
||||
|
||||
/* TODO c'est quoi ce nettoyage de cache ?
|
||||
if (!Util.isOffline(PodcastsActivity.this))
|
||||
new CacheCleaner(PodcastsActivity.this, getDownloadService()).cleanPlaylists(playlists);
|
||||
*/
|
||||
return channels;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<PodcastsChannel> result)
|
||||
{
|
||||
channelItemsListView.setAdapter(new PodcastsChannelsAdapter(currentActivity, result));
|
||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
|
||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* Receives search queries and forwards to the SelectAlbumActivity.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class QueryReceiverActivity extends ResultActivity
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String query = getIntent().getStringExtra(SearchManager.QUERY);
|
||||
|
||||
if (query != null)
|
||||
{
|
||||
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE);
|
||||
suggestions.saveRecentQuery(query, null);
|
||||
|
||||
Intent intent = new Intent(QueryReceiverActivity.this, SearchActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_QUERY, query);
|
||||
startActivityForResultWithoutTransition(QueryReceiverActivity.this, intent);
|
||||
}
|
||||
finish();
|
||||
Util.disablePendingTransition(this);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package org.moire.ultrasonic.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* Created by Joshua Bahnsen on 12/30/13.
|
||||
*/
|
||||
public class ResultActivity extends AppCompatActivity
|
||||
{
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
switch (resultCode)
|
||||
{
|
||||
case Constants.RESULT_CLOSE_ALL:
|
||||
setResult(Constants.RESULT_CLOSE_ALL);
|
||||
finish();
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public void startActivityForResultWithoutTransition(Activity currentActivity, Class<? extends Activity> newActivity)
|
||||
{
|
||||
startActivityForResultWithoutTransition(currentActivity, new Intent(currentActivity, newActivity));
|
||||
}
|
||||
|
||||
public void startActivityForResultWithoutTransition(Activity currentActivity, Intent intent)
|
||||
{
|
||||
startActivityForResult(intent, 0);
|
||||
Util.disablePendingTransition(currentActivity);
|
||||
}
|
||||
}
|
@ -1,547 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.Artist;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
import org.moire.ultrasonic.domain.SearchCriteria;
|
||||
import org.moire.ultrasonic.domain.SearchResult;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.MergeAdapter;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.ArtistAdapter;
|
||||
import org.moire.ultrasonic.view.EntryAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Performs searches and displays the matching artists, albums and songs.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class SearchActivity extends SubsonicTabActivity
|
||||
{
|
||||
|
||||
private static int DEFAULT_ARTISTS;
|
||||
private static int DEFAULT_ALBUMS;
|
||||
private static int DEFAULT_SONGS;
|
||||
|
||||
private ListView list;
|
||||
|
||||
private View artistsHeading;
|
||||
private View albumsHeading;
|
||||
private View songsHeading;
|
||||
private TextView searchButton;
|
||||
private View moreArtistsButton;
|
||||
private View moreAlbumsButton;
|
||||
private View moreSongsButton;
|
||||
private SearchResult searchResult;
|
||||
private MergeAdapter mergeAdapter;
|
||||
private ArtistAdapter artistAdapter;
|
||||
private ListAdapter moreArtistsAdapter;
|
||||
private EntryAdapter albumAdapter;
|
||||
private ListAdapter moreAlbumsAdapter;
|
||||
private ListAdapter moreSongsAdapter;
|
||||
private EntryAdapter songAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.search);
|
||||
|
||||
setActionBarTitle(R.string.common_appname);
|
||||
setActionBarSubtitle(R.string.search_title);
|
||||
|
||||
View searchMenuItem = findViewById(R.id.menu_search);
|
||||
menuDrawer.setActiveView(searchMenuItem);
|
||||
|
||||
DEFAULT_ARTISTS = Util.getDefaultArtists(this);
|
||||
DEFAULT_ALBUMS = Util.getDefaultAlbums(this);
|
||||
DEFAULT_SONGS = Util.getDefaultSongs(this);
|
||||
|
||||
View buttons = LayoutInflater.from(this).inflate(R.layout.search_buttons, null);
|
||||
|
||||
if (buttons != null)
|
||||
{
|
||||
artistsHeading = buttons.findViewById(R.id.search_artists);
|
||||
albumsHeading = buttons.findViewById(R.id.search_albums);
|
||||
songsHeading = buttons.findViewById(R.id.search_songs);
|
||||
searchButton = (TextView) buttons.findViewById(R.id.search_search);
|
||||
moreArtistsButton = buttons.findViewById(R.id.search_more_artists);
|
||||
moreAlbumsButton = buttons.findViewById(R.id.search_more_albums);
|
||||
moreSongsButton = buttons.findViewById(R.id.search_more_songs);
|
||||
}
|
||||
|
||||
list = (ListView) findViewById(R.id.search_list);
|
||||
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (view == searchButton)
|
||||
{
|
||||
onSearchRequested();
|
||||
}
|
||||
else if (view == moreArtistsButton)
|
||||
{
|
||||
expandArtists();
|
||||
}
|
||||
else if (view == moreAlbumsButton)
|
||||
{
|
||||
expandAlbums();
|
||||
}
|
||||
else if (view == moreSongsButton)
|
||||
{
|
||||
expandSongs();
|
||||
}
|
||||
else
|
||||
{
|
||||
Object item = parent.getItemAtPosition(position);
|
||||
if (item instanceof Artist)
|
||||
{
|
||||
onArtistSelected((Artist) item);
|
||||
}
|
||||
else if (item instanceof MusicDirectory.Entry)
|
||||
{
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) item;
|
||||
if (entry.isDirectory())
|
||||
{
|
||||
onAlbumSelected(entry, false);
|
||||
}
|
||||
else if (entry.isVideo())
|
||||
{
|
||||
onVideoSelected(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
onSongSelected(entry, false, true, true, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(list);
|
||||
|
||||
onNewIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
String query = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_QUERY);
|
||||
boolean autoPlay = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
|
||||
boolean requestSearch = intent.getBooleanExtra(Constants.INTENT_EXTRA_REQUEST_SEARCH, false);
|
||||
|
||||
if (query != null)
|
||||
{
|
||||
mergeAdapter = new MergeAdapter();
|
||||
list.setAdapter(mergeAdapter);
|
||||
search(query, autoPlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
populateList();
|
||||
if (requestSearch) onSearchRequested();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
Object selectedItem = list.getItemAtPosition(info.position);
|
||||
|
||||
boolean isArtist = selectedItem instanceof Artist;
|
||||
boolean isAlbum = selectedItem instanceof MusicDirectory.Entry && ((MusicDirectory.Entry) selectedItem).isDirectory();
|
||||
|
||||
if (!isArtist && !isAlbum)
|
||||
{
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.select_song_context, menu);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.select_album_context, menu);
|
||||
}
|
||||
|
||||
MenuItem shareButton = menu.findItem(R.id.menu_item_share);
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
|
||||
|
||||
if (downloadMenuItem != null)
|
||||
{
|
||||
downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this));
|
||||
}
|
||||
|
||||
if (ActiveServerProvider.Companion.isOffline(this) || isArtist)
|
||||
{
|
||||
if (shareButton != null)
|
||||
{
|
||||
shareButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Object selectedItem = list.getItemAtPosition(info.position);
|
||||
|
||||
Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null;
|
||||
Entry entry = selectedItem instanceof Entry ? (Entry) selectedItem : null;
|
||||
|
||||
String entryId = null;
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
entryId = entry.getId();
|
||||
}
|
||||
|
||||
String id = artist != null ? artist.getId() : entryId;
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Entry> songs = new ArrayList<Entry>(1);
|
||||
|
||||
switch (menuItem.getItemId())
|
||||
{
|
||||
case R.id.album_menu_play_now:
|
||||
downloadRecursively(id, false, false, true, false, false, false, false, false);
|
||||
break;
|
||||
case R.id.album_menu_play_next:
|
||||
downloadRecursively(id, false, true, false, true, false, true, false, false);
|
||||
break;
|
||||
case R.id.album_menu_play_last:
|
||||
downloadRecursively(id, false, true, false, false, false, false, false, false);
|
||||
break;
|
||||
case R.id.album_menu_pin:
|
||||
downloadRecursively(id, true, true, false, false, false, false, false, false);
|
||||
break;
|
||||
case R.id.album_menu_unpin:
|
||||
downloadRecursively(id, false, false, false, false, false, false, true, false);
|
||||
break;
|
||||
case R.id.album_menu_download:
|
||||
downloadRecursively(id, false, false, false, false, true, false, false, false);
|
||||
break;
|
||||
case R.id.song_menu_play_now:
|
||||
if (entry != null)
|
||||
{
|
||||
songs = new ArrayList<MusicDirectory.Entry>(1);
|
||||
songs.add(entry);
|
||||
download(false, false, true, false, false, songs);
|
||||
}
|
||||
break;
|
||||
case R.id.song_menu_play_next:
|
||||
if (entry != null)
|
||||
{
|
||||
songs = new ArrayList<MusicDirectory.Entry>(1);
|
||||
songs.add(entry);
|
||||
download(true, false, false, true, false, songs);
|
||||
}
|
||||
break;
|
||||
case R.id.song_menu_play_last:
|
||||
if (entry != null)
|
||||
{
|
||||
songs = new ArrayList<MusicDirectory.Entry>(1);
|
||||
songs.add(entry);
|
||||
download(true, false, false, false, false, songs);
|
||||
}
|
||||
break;
|
||||
case R.id.song_menu_pin:
|
||||
if (entry != null)
|
||||
{
|
||||
songs.add(entry);
|
||||
Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size()));
|
||||
downloadBackground(true, songs);
|
||||
}
|
||||
break;
|
||||
case R.id.song_menu_download:
|
||||
if (entry != null)
|
||||
{
|
||||
songs.add(entry);
|
||||
Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size()));
|
||||
downloadBackground(false, songs);
|
||||
}
|
||||
break;
|
||||
case R.id.song_menu_unpin:
|
||||
if (entry != null)
|
||||
{
|
||||
songs.add(entry);
|
||||
Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size()));
|
||||
getMediaPlayerController().unpin(songs);
|
||||
}
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
if (entry != null)
|
||||
{
|
||||
songs = new ArrayList<MusicDirectory.Entry>(1);
|
||||
songs.add(entry);
|
||||
createShare(songs);
|
||||
}
|
||||
default:
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
if (getMediaPlayerController() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable onValid = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
getMediaPlayerController().downloadBackground(songs, save);
|
||||
}
|
||||
};
|
||||
|
||||
checkLicenseAndTrialPeriod(onValid);
|
||||
}
|
||||
|
||||
private void search(final String query, final boolean autoplay)
|
||||
{
|
||||
final int maxArtists = Util.getMaxArtists(this);
|
||||
final int maxAlbums = Util.getMaxAlbums(this);
|
||||
final int maxSongs = Util.getMaxSongs(this);
|
||||
|
||||
BackgroundTask<SearchResult> task = new TabActivityBackgroundTask<SearchResult>(this, true)
|
||||
{
|
||||
@Override
|
||||
protected SearchResult doInBackground() throws Throwable
|
||||
{
|
||||
SearchCriteria criteria = new SearchCriteria(query, maxArtists, maxAlbums, maxSongs);
|
||||
MusicService service = MusicServiceFactory.getMusicService(SearchActivity.this);
|
||||
licenseValid = service.isLicenseValid(SearchActivity.this, this);
|
||||
return service.search(criteria, SearchActivity.this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(SearchResult result)
|
||||
{
|
||||
searchResult = result;
|
||||
|
||||
populateList();
|
||||
|
||||
if (autoplay)
|
||||
{
|
||||
autoplay();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
private void populateList()
|
||||
{
|
||||
mergeAdapter = new MergeAdapter();
|
||||
mergeAdapter.addView(searchButton, true);
|
||||
|
||||
if (searchResult != null)
|
||||
{
|
||||
List<Artist> artists = searchResult.getArtists();
|
||||
if (!artists.isEmpty())
|
||||
{
|
||||
mergeAdapter.addView(artistsHeading);
|
||||
List<Artist> displayedArtists = new ArrayList<Artist>(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size())));
|
||||
artistAdapter = new ArtistAdapter(this, displayedArtists);
|
||||
mergeAdapter.addAdapter(artistAdapter);
|
||||
if (artists.size() > DEFAULT_ARTISTS)
|
||||
{
|
||||
moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<MusicDirectory.Entry> albums = searchResult.getAlbums();
|
||||
if (!albums.isEmpty())
|
||||
{
|
||||
mergeAdapter.addView(albumsHeading);
|
||||
List<MusicDirectory.Entry> displayedAlbums = new ArrayList<MusicDirectory.Entry>(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size())));
|
||||
albumAdapter = new EntryAdapter(this, getImageLoader(), displayedAlbums, false);
|
||||
mergeAdapter.addAdapter(albumAdapter);
|
||||
if (albums.size() > DEFAULT_ALBUMS)
|
||||
{
|
||||
moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<MusicDirectory.Entry> songs = searchResult.getSongs();
|
||||
if (!songs.isEmpty())
|
||||
{
|
||||
mergeAdapter.addView(songsHeading);
|
||||
List<MusicDirectory.Entry> displayedSongs = new ArrayList<MusicDirectory.Entry>(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size())));
|
||||
songAdapter = new EntryAdapter(this, getImageLoader(), displayedSongs, false);
|
||||
mergeAdapter.addAdapter(songAdapter);
|
||||
if (songs.size() > DEFAULT_SONGS)
|
||||
{
|
||||
moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty();
|
||||
searchButton.setText(empty ? R.string.search_no_match : R.string.search_search);
|
||||
}
|
||||
|
||||
list.setAdapter(mergeAdapter);
|
||||
}
|
||||
|
||||
private void expandArtists()
|
||||
{
|
||||
artistAdapter.clear();
|
||||
|
||||
for (Artist artist : searchResult.getArtists())
|
||||
{
|
||||
artistAdapter.add(artist);
|
||||
}
|
||||
|
||||
artistAdapter.notifyDataSetChanged();
|
||||
mergeAdapter.removeAdapter(moreArtistsAdapter);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void expandAlbums()
|
||||
{
|
||||
albumAdapter.clear();
|
||||
|
||||
for (MusicDirectory.Entry album : searchResult.getAlbums())
|
||||
{
|
||||
albumAdapter.add(album);
|
||||
}
|
||||
|
||||
albumAdapter.notifyDataSetChanged();
|
||||
mergeAdapter.removeAdapter(moreAlbumsAdapter);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void expandSongs()
|
||||
{
|
||||
songAdapter.clear();
|
||||
|
||||
for (MusicDirectory.Entry song : searchResult.getSongs())
|
||||
{
|
||||
songAdapter.add(song);
|
||||
}
|
||||
|
||||
songAdapter.notifyDataSetChanged();
|
||||
mergeAdapter.removeAdapter(moreSongsAdapter);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void onArtistSelected(Artist artist)
|
||||
{
|
||||
Intent intent = new Intent(this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.getName());
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay)
|
||||
{
|
||||
Intent intent = new Intent(SearchActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, album.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_IS_ALBUM, album.isDirectory());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay);
|
||||
startActivityForResultWithoutTransition(SearchActivity.this, intent);
|
||||
}
|
||||
|
||||
private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext)
|
||||
{
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
if (mediaPlayerController != null)
|
||||
{
|
||||
if (!append && !playNext)
|
||||
{
|
||||
mediaPlayerController.clear();
|
||||
}
|
||||
|
||||
mediaPlayerController.download(Collections.singletonList(song), save, false, playNext, false, false);
|
||||
|
||||
if (autoplay)
|
||||
{
|
||||
mediaPlayerController.play(mediaPlayerController.getPlaylistSize() - 1);
|
||||
}
|
||||
|
||||
Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void onVideoSelected(MusicDirectory.Entry entry)
|
||||
{
|
||||
playVideo(entry);
|
||||
}
|
||||
|
||||
private void autoplay()
|
||||
{
|
||||
if (!searchResult.getSongs().isEmpty())
|
||||
{
|
||||
onSongSelected(searchResult.getSongs().get(0), false, false, true, false);
|
||||
}
|
||||
else if (!searchResult.getAlbums().isEmpty())
|
||||
{
|
||||
onAlbumSelected(searchResult.getAlbums().get(0), true);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,191 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import timber.log.Timber;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.Genre;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.GenreAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SelectGenreActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener
|
||||
{
|
||||
private SwipeRefreshLayout refreshGenreListView;
|
||||
private ListView genreListView;
|
||||
private View emptyView;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.select_genre);
|
||||
|
||||
refreshGenreListView = findViewById(R.id.select_genre_refresh);
|
||||
genreListView = findViewById(R.id.select_genre_list);
|
||||
|
||||
refreshGenreListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
});
|
||||
|
||||
genreListView.setOnItemClickListener(this);
|
||||
|
||||
emptyView = findViewById(R.id.select_genre_empty);
|
||||
|
||||
registerForContextMenu(genreListView);
|
||||
|
||||
View browseMenuItem = findViewById(R.id.menu_browse);
|
||||
menuDrawer.setActiveView(browseMenuItem);
|
||||
|
||||
setActionBarSubtitle(R.string.main_genres_title);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
finish();
|
||||
Intent intent = getIntent();
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
BackgroundTask<List<Genre>> task = new TabActivityBackgroundTask<List<Genre>>(this, true)
|
||||
{
|
||||
@Override
|
||||
protected List<Genre> doInBackground() throws Throwable
|
||||
{
|
||||
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectGenreActivity.this);
|
||||
|
||||
List<Genre> genres = new ArrayList<Genre>();
|
||||
|
||||
try
|
||||
{
|
||||
genres = musicService.getGenres(refresh, SelectGenreActivity.this, this);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
Timber.e(x, "Failed to load genres");
|
||||
}
|
||||
|
||||
return genres;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<Genre> result)
|
||||
{
|
||||
emptyView.setVisibility(result == null || result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
genreListView.setAdapter(new GenreAdapter(SelectGenreActivity.this, result));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Genre genre = (Genre) parent.getItemAtPosition(position);
|
||||
|
||||
if (genre != null)
|
||||
{
|
||||
Intent intent = new Intent(this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre.getName());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(this));
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
menuDrawer.toggleMenu();
|
||||
return true;
|
||||
case R.id.main_shuffle:
|
||||
Intent intent = new Intent(this, DownloadActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class GetDataTask extends AsyncTask<Void, Void, String[]>
|
||||
{
|
||||
@Override
|
||||
protected void onPostExecute(String[] result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] doInBackground(Void... params)
|
||||
{
|
||||
refresh();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,391 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.Playlist;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.service.OfflineException;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CacheCleaner;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.LoadingTask;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.PlaylistAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SelectPlaylistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener
|
||||
{
|
||||
|
||||
private SwipeRefreshLayout refreshPlaylistsListView;
|
||||
private ListView playlistsListView;
|
||||
private View emptyTextView;
|
||||
private PlaylistAdapter playlistAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.select_playlist);
|
||||
|
||||
refreshPlaylistsListView = findViewById(R.id.select_playlist_refresh);
|
||||
playlistsListView = findViewById(R.id.select_playlist_list);
|
||||
|
||||
refreshPlaylistsListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
});
|
||||
|
||||
emptyTextView = findViewById(R.id.select_playlist_empty);
|
||||
playlistsListView.setOnItemClickListener(this);
|
||||
registerForContextMenu(playlistsListView);
|
||||
|
||||
View playlistsMenuItem = findViewById(R.id.menu_playlists);
|
||||
menuDrawer.setActiveView(playlistsMenuItem);
|
||||
|
||||
setActionBarTitle(R.string.common_appname);
|
||||
setActionBarSubtitle(R.string.playlist_label);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
super.onCreateOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
finish();
|
||||
Intent intent = new Intent(this, SelectPlaylistActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
BackgroundTask<List<Playlist>> task = new TabActivityBackgroundTask<List<Playlist>>(this, true)
|
||||
{
|
||||
@Override
|
||||
protected List<Playlist> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
||||
List<Playlist> playlists = musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this);
|
||||
|
||||
if (!ActiveServerProvider.Companion.isOffline(SelectPlaylistActivity.this))
|
||||
new CacheCleaner(SelectPlaylistActivity.this).cleanPlaylists(playlists);
|
||||
return playlists;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<Playlist> result)
|
||||
{
|
||||
playlistsListView.setAdapter(playlistAdapter = new PlaylistAdapter(SelectPlaylistActivity.this, result));
|
||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
if (ActiveServerProvider.Companion.isOffline(this)) inflater.inflate(R.menu.select_playlist_context_offline, menu);
|
||||
else inflater.inflate(R.menu.select_playlist_context, menu);
|
||||
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
|
||||
|
||||
if (downloadMenuItem != null)
|
||||
{
|
||||
downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
if (info == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position);
|
||||
if (playlist == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Intent intent;
|
||||
switch (menuItem.getItemId())
|
||||
{
|
||||
case R.id.playlist_menu_pin:
|
||||
downloadPlaylist(playlist.getId(), playlist.getName(), true, true, false, false, true, false, false);
|
||||
break;
|
||||
case R.id.playlist_menu_unpin:
|
||||
downloadPlaylist(playlist.getId(), playlist.getName(), false, false, false, false, true, false, true);
|
||||
break;
|
||||
case R.id.playlist_menu_download:
|
||||
downloadPlaylist(playlist.getId(), playlist.getName(), false, false, false, false, true, false, false);
|
||||
break;
|
||||
case R.id.playlist_menu_play_now:
|
||||
intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
startActivityForResultWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||
break;
|
||||
case R.id.playlist_menu_play_shuffled:
|
||||
intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||
startActivityForResultWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||
break;
|
||||
case R.id.playlist_menu_delete:
|
||||
deletePlaylist(playlist);
|
||||
break;
|
||||
case R.id.playlist_info:
|
||||
displayPlaylistInfo(playlist);
|
||||
break;
|
||||
case R.id.playlist_update_info:
|
||||
updatePlaylistInfo(playlist);
|
||||
break;
|
||||
default:
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
menuDrawer.toggleMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Playlist playlist = (Playlist) parent.getItemAtPosition(position);
|
||||
|
||||
if (playlist == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, playlist.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
startActivityForResultWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||
}
|
||||
|
||||
private void deletePlaylist(final Playlist playlist)
|
||||
{
|
||||
new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, playlist.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(SelectPlaylistActivity.this, false)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||
musicService.deletePlaylist(playlist.getId(), SelectPlaylistActivity.this, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
playlistAdapter.remove(playlist);
|
||||
playlistAdapter.notifyDataSetChanged();
|
||||
Util.toast(SelectPlaylistActivity.this, getResources().getString(R.string.menu_deleted_playlist, playlist.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(SelectPlaylistActivity.this, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
}).setNegativeButton(R.string.common_cancel, null).show();
|
||||
}
|
||||
|
||||
private void displayPlaylistInfo(final Playlist playlist)
|
||||
{
|
||||
final TextView textView = new TextView(this);
|
||||
textView.setPadding(5, 5, 5, 5);
|
||||
|
||||
final Spannable message = new SpannableString("Owner: " + playlist.getOwner() + "\nComments: " +
|
||||
((playlist.getComment() == null) ? "" : playlist.getComment()) +
|
||||
"\nSong Count: " + playlist.getSongCount() +
|
||||
((playlist.getPublic() == null) ? "" : ("\nPublic: " + playlist.getPublic()) + ((playlist.getCreated() == null) ? "" : ("\nCreation Date: " + playlist.getCreated().replace('T', ' ')))));
|
||||
|
||||
Linkify.addLinks(message, Linkify.WEB_URLS);
|
||||
textView.setText(message);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
new AlertDialog.Builder(this).setTitle(playlist.getName()).setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show();
|
||||
}
|
||||
|
||||
private void updatePlaylistInfo(final Playlist playlist)
|
||||
{
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.update_playlist, null);
|
||||
|
||||
if (dialogView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final EditText nameBox = (EditText) dialogView.findViewById(R.id.get_playlist_name);
|
||||
final EditText commentBox = (EditText) dialogView.findViewById(R.id.get_playlist_comment);
|
||||
final CheckBox publicBox = (CheckBox) dialogView.findViewById(R.id.get_playlist_public);
|
||||
|
||||
nameBox.setText(playlist.getName());
|
||||
commentBox.setText(playlist.getComment());
|
||||
Boolean pub = playlist.getPublic();
|
||||
|
||||
if (pub == null)
|
||||
{
|
||||
publicBox.setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
publicBox.setChecked(pub);
|
||||
}
|
||||
|
||||
AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
|
||||
|
||||
alertDialog.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alertDialog.setTitle(R.string.playlist_update_info);
|
||||
alertDialog.setView(dialogView);
|
||||
alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(SelectPlaylistActivity.this, false)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
Editable nameBoxText = nameBox.getText();
|
||||
Editable commentBoxText = commentBox.getText();
|
||||
String name = nameBoxText != null ? nameBoxText.toString() : null;
|
||||
String comment = commentBoxText != null ? commentBoxText.toString() : null;
|
||||
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||
musicService.updatePlaylist(playlist.getId(), name, comment, publicBox.isChecked(), SelectPlaylistActivity.this, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
refresh();
|
||||
Util.toast(SelectPlaylistActivity.this, getResources().getString(R.string.playlist_updated_info, playlist.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, playlist.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(SelectPlaylistActivity.this, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
});
|
||||
alertDialog.setNegativeButton(R.string.common_cancel, null);
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private class GetDataTask extends AsyncTask<Void, Void, String[]>
|
||||
{
|
||||
@Override
|
||||
protected void onPostExecute(String[] result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] doInBackground(Void... params)
|
||||
{
|
||||
refresh();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.fragment.SettingsFragment;
|
||||
|
||||
public class SettingsActivity extends SubsonicTabActivity {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null)
|
||||
{
|
||||
actionBar.setSubtitle(R.string.menu_settings);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,385 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.domain.Share;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.service.OfflineException;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.LoadingTask;
|
||||
import org.moire.ultrasonic.util.TabActivityBackgroundTask;
|
||||
import org.moire.ultrasonic.util.TimeSpan;
|
||||
import org.moire.ultrasonic.util.TimeSpanPicker;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.ShareAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ShareActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener
|
||||
{
|
||||
|
||||
private SwipeRefreshLayout refreshSharesListView;
|
||||
private ListView sharesListView;
|
||||
private View emptyTextView;
|
||||
private ShareAdapter shareAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.select_share);
|
||||
|
||||
refreshSharesListView = findViewById(R.id.select_share_refresh);
|
||||
sharesListView = findViewById(R.id.select_share_list);
|
||||
|
||||
refreshSharesListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
new GetDataTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
});
|
||||
|
||||
emptyTextView = findViewById(R.id.select_share_empty);
|
||||
sharesListView.setOnItemClickListener(this);
|
||||
registerForContextMenu(sharesListView);
|
||||
|
||||
View sharesMenuItem = findViewById(R.id.menu_shares);
|
||||
menuDrawer.setActiveView(sharesMenuItem);
|
||||
|
||||
setActionBarTitle(R.string.common_appname);
|
||||
setActionBarSubtitle(R.string.button_bar_shares);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
super.onCreateOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refresh()
|
||||
{
|
||||
finish();
|
||||
Intent intent = new Intent(this, ShareActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_REFRESH, true);
|
||||
startActivityForResultWithoutTransition(this, intent);
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
BackgroundTask<List<Share>> task = new TabActivityBackgroundTask<List<Share>>(this, true)
|
||||
{
|
||||
@Override
|
||||
protected List<Share> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(ShareActivity.this);
|
||||
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
||||
return musicService.getShares(refresh, ShareActivity.this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<Share> result)
|
||||
{
|
||||
sharesListView.setAdapter(shareAdapter = new ShareAdapter(ShareActivity.this, result));
|
||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.select_share_context, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
if (info == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Share share = (Share) sharesListView.getItemAtPosition(info.position);
|
||||
if (share == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (menuItem.getItemId())
|
||||
{
|
||||
case R.id.share_menu_pin:
|
||||
downloadShare(share.getId(), share.getName(), true, true, false, false, true, false, false);
|
||||
break;
|
||||
case R.id.share_menu_unpin:
|
||||
downloadShare(share.getId(), share.getName(), false, false, false, false, true, false, true);
|
||||
break;
|
||||
case R.id.share_menu_download:
|
||||
downloadShare(share.getId(), share.getName(), false, false, false, false, true, false, false);
|
||||
break;
|
||||
case R.id.share_menu_play_now:
|
||||
downloadShare(share.getId(), share.getName(), false, false, true, false, false, false, false);
|
||||
break;
|
||||
case R.id.share_menu_play_shuffled:
|
||||
downloadShare(share.getId(), share.getName(), false, false, true, true, false, false, false);
|
||||
break;
|
||||
case R.id.share_menu_delete:
|
||||
deleteShare(share);
|
||||
break;
|
||||
case R.id.share_info:
|
||||
displayShareInfo(share);
|
||||
break;
|
||||
case R.id.share_update_info:
|
||||
updateShareInfo(share);
|
||||
break;
|
||||
default:
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
switch (item.getItemId())
|
||||
{
|
||||
case android.R.id.home:
|
||||
menuDrawer.toggleMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
Share share = (Share) parent.getItemAtPosition(position);
|
||||
|
||||
if (share == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(ShareActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHARE_ID, share.getId());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHARE_NAME, share.getName());
|
||||
startActivityForResultWithoutTransition(ShareActivity.this, intent);
|
||||
}
|
||||
|
||||
private void deleteShare(final Share share)
|
||||
{
|
||||
new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, share.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(ShareActivity.this, false)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(ShareActivity.this);
|
||||
musicService.deleteShare(share.getId(), ShareActivity.this, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
shareAdapter.remove(share);
|
||||
shareAdapter.notifyDataSetChanged();
|
||||
Util.toast(ShareActivity.this, getResources().getString(R.string.menu_deleted_share, share.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_share_error, share.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(ShareActivity.this, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
}).setNegativeButton(R.string.common_cancel, null).show();
|
||||
}
|
||||
|
||||
private void displayShareInfo(final Share share)
|
||||
{
|
||||
final TextView textView = new TextView(this);
|
||||
textView.setPadding(5, 5, 5, 5);
|
||||
|
||||
final Spannable message = new SpannableString("Owner: " + share.getUsername() +
|
||||
"\nComments: " + ((share.getDescription() == null) ? "" : share.getDescription()) +
|
||||
"\nURL: " + share.getUrl() +
|
||||
"\nEntry Count: " + share.getEntries().size() +
|
||||
"\nVisit Count: " + share.getVisitCount() +
|
||||
((share.getCreated() == null) ? "" : ("\nCreation Date: " + share.getCreated().replace('T', ' '))) +
|
||||
((share.getLastVisited() == null) ? "" : ("\nLast Visited Date: " + share.getLastVisited().replace('T', ' '))) +
|
||||
((share.getExpires() == null) ? "" : ("\nExpiration Date: " + share.getExpires().replace('T', ' '))));
|
||||
|
||||
Linkify.addLinks(message, Linkify.WEB_URLS);
|
||||
textView.setText(message);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
new AlertDialog.Builder(this).setTitle("Share Details").setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show();
|
||||
}
|
||||
|
||||
private void updateShareInfo(final Share share)
|
||||
{
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.share_details, null);
|
||||
if (dialogView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final EditText shareDescription = (EditText) dialogView.findViewById(R.id.share_description);
|
||||
final TimeSpanPicker timeSpanPicker = (TimeSpanPicker) dialogView.findViewById(R.id.date_picker);
|
||||
|
||||
shareDescription.setText(share.getDescription());
|
||||
|
||||
CheckBox hideDialogCheckBox = (CheckBox) dialogView.findViewById(R.id.hide_dialog);
|
||||
CheckBox saveAsDefaultsCheckBox = (CheckBox) dialogView.findViewById(R.id.save_as_defaults);
|
||||
CheckBox noExpirationCheckBox = (CheckBox) dialogView.findViewById(R.id.timeSpanDisableCheckBox);
|
||||
|
||||
noExpirationCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
|
||||
{
|
||||
timeSpanPicker.setEnabled(!b);
|
||||
}
|
||||
});
|
||||
|
||||
noExpirationCheckBox.setChecked(true);
|
||||
|
||||
timeSpanPicker.setTimeSpanDisableText(getResources().getText(R.string.no_expiration));
|
||||
|
||||
hideDialogCheckBox.setVisibility(View.GONE);
|
||||
saveAsDefaultsCheckBox.setVisibility(View.GONE);
|
||||
|
||||
AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
|
||||
|
||||
alertDialog.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alertDialog.setTitle(R.string.playlist_update_info);
|
||||
alertDialog.setView(dialogView);
|
||||
alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(ShareActivity.this, false)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
long millis = timeSpanPicker.getTimeSpan().getTotalMilliseconds();
|
||||
|
||||
if (millis > 0)
|
||||
{
|
||||
millis = TimeSpan.getCurrentTime().add(millis).getTotalMilliseconds();
|
||||
}
|
||||
|
||||
Editable shareDescriptionText = shareDescription.getText();
|
||||
String description = shareDescriptionText != null ? shareDescriptionText.toString() : null;
|
||||
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(ShareActivity.this);
|
||||
musicService.updateShare(share.getId(), description, millis, ShareActivity.this, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
refresh();
|
||||
Util.toast(ShareActivity.this, getResources().getString(R.string.playlist_updated_info, share.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, share.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(ShareActivity.this, msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
alertDialog.setNegativeButton(R.string.common_cancel, null);
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private class GetDataTask extends AsyncTask<Void, Void, String[]>
|
||||
{
|
||||
@Override
|
||||
protected void onPostExecute(String[] result)
|
||||
{
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] doInBackground(Void... params)
|
||||
{
|
||||
refresh();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,61 +0,0 @@
|
||||
/*
|
||||
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 org.moire.ultrasonic.activity;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
|
||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* Receives voice search queries and forwards to the SearchActivity.
|
||||
* <p/>
|
||||
* http://android-developers.blogspot.com/2010/09/supporting-new-music-voice-action.html
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class VoiceQueryReceiverActivity extends ResultActivity
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String query = getIntent().getStringExtra(SearchManager.QUERY);
|
||||
|
||||
if (query != null)
|
||||
{
|
||||
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE);
|
||||
suggestions.saveRecentQuery(query, null);
|
||||
|
||||
Intent intent = new Intent(VoiceQueryReceiverActivity.this, SearchActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_QUERY, query);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
startActivityForResultWithoutTransition(VoiceQueryReceiverActivity.this, intent);
|
||||
}
|
||||
finish();
|
||||
Util.disablePendingTransition(this);
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ import java.io.Serializable;
|
||||
public class EqualizerController
|
||||
{
|
||||
private static Boolean available = null;
|
||||
private static MutableLiveData<EqualizerController> instance = new MutableLiveData<>();
|
||||
private static final MutableLiveData<EqualizerController> instance = new MutableLiveData<>();
|
||||
|
||||
private Context context;
|
||||
public Equalizer equalizer;
|
||||
|
@ -36,7 +36,7 @@ public class VisualizerController
|
||||
{
|
||||
private static final int PREFERRED_CAPTURE_SIZE = 128; // Must be a power of two.
|
||||
private static Boolean available = null;
|
||||
private static MutableLiveData<VisualizerController> instance = new MutableLiveData<>();
|
||||
private static final MutableLiveData<VisualizerController> instance = new MutableLiveData<>();
|
||||
|
||||
public Visualizer visualizer;
|
||||
private int audioSessionId;
|
||||
|
@ -0,0 +1,149 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* Displays online help and about information in a webWiew
|
||||
*/
|
||||
public class AboutFragment extends Fragment {
|
||||
|
||||
private WebView webView;
|
||||
private ImageView backButton;
|
||||
private ImageView forwardButton;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.help, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
swipeRefresh = view.findViewById(R.id.help_refresh);
|
||||
swipeRefresh.setEnabled(false);
|
||||
|
||||
webView = view.findViewById(R.id.help_contents);
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.setWebViewClient(new HelpClient());
|
||||
|
||||
if (savedInstanceState != null)
|
||||
{
|
||||
webView.restoreState(savedInstanceState);
|
||||
}
|
||||
else
|
||||
{
|
||||
webView.loadUrl(getResources().getString(R.string.help_url));
|
||||
}
|
||||
|
||||
backButton = view.findViewById(R.id.help_back);
|
||||
backButton.setOnClickListener(new Button.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
webView.goBack();
|
||||
}
|
||||
});
|
||||
|
||||
ImageView stopButton = view.findViewById(R.id.help_stop);
|
||||
stopButton.setOnClickListener(new Button.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
webView.stopLoading();
|
||||
swipeRefresh.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
|
||||
forwardButton = view.findViewById(R.id.help_forward);
|
||||
forwardButton.setOnClickListener(new Button.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
webView.goForward();
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Nicer Back key handling?
|
||||
webView.setFocusableInTouchMode(true);
|
||||
webView.requestFocus();
|
||||
webView.setOnKeyListener( new View.OnKeyListener()
|
||||
{
|
||||
@Override
|
||||
public boolean onKey( View v, int keyCode, KeyEvent event )
|
||||
{
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
if (webView.canGoBack())
|
||||
{
|
||||
webView.goBack();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NotNull Bundle state)
|
||||
{
|
||||
webView.saveState(state);
|
||||
super.onSaveInstanceState(state);
|
||||
}
|
||||
|
||||
private final class HelpClient extends WebViewClient
|
||||
{
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
swipeRefresh.setRefreshing(true);
|
||||
super.onPageStarted(view, url, favicon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url)
|
||||
{
|
||||
swipeRefresh.setRefreshing(false);
|
||||
String versionName = Util.getVersionName(getContext());
|
||||
String title = String.format("%s (%s)", view.getTitle(), versionName);
|
||||
|
||||
FragmentTitle.Companion.setTitle(AboutFragment.this, title);
|
||||
|
||||
backButton.setEnabled(view.canGoBack());
|
||||
forwardButton.setEnabled(view.canGoForward());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
|
||||
{
|
||||
Util.toast(getContext(), description);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,436 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.service.DownloadFile;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
|
||||
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker;
|
||||
import org.moire.ultrasonic.subsonic.VideoPlayer;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Pair;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.EntryAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Lists the Bookmarks available on the server
|
||||
*/
|
||||
public class BookmarksFragment extends Fragment {
|
||||
|
||||
private SwipeRefreshLayout refreshAlbumListView;
|
||||
private ListView albumListView;
|
||||
private View albumButtons;
|
||||
private View emptyView;
|
||||
private ImageView playNowButton;
|
||||
private ImageView pinButton;
|
||||
private ImageView unpinButton;
|
||||
private ImageView downloadButton;
|
||||
private ImageView deleteButton;
|
||||
|
||||
private final Lazy<VideoPlayer> videoPlayer = inject(VideoPlayer.class);
|
||||
private final Lazy<MediaPlayerController> mediaPlayerController = inject(MediaPlayerController.class);
|
||||
private final Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class);
|
||||
private final Lazy<NetworkAndStorageChecker> networkAndStorageChecker = inject(NetworkAndStorageChecker.class);
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.select_album, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
cancellationToken = new CancellationToken();
|
||||
albumButtons = view.findViewById(R.id.menu_album);
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
refreshAlbumListView = view.findViewById(R.id.select_album_entries_refresh);
|
||||
albumListView = view.findViewById(R.id.select_album_entries_list);
|
||||
|
||||
refreshAlbumListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
enableButtons();
|
||||
getBookmarks();
|
||||
}
|
||||
});
|
||||
|
||||
albumListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
|
||||
albumListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (position >= 0)
|
||||
{
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) parent.getItemAtPosition(position);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
if (entry.isVideo())
|
||||
{
|
||||
videoPlayer.getValue().playVideo(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
enableButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ImageView selectButton = view.findViewById(R.id.select_album_select);
|
||||
playNowButton = view.findViewById(R.id.select_album_play_now);
|
||||
ImageView playNextButton = view.findViewById(R.id.select_album_play_next);
|
||||
ImageView playLastButton = view.findViewById(R.id.select_album_play_last);
|
||||
pinButton = view.findViewById(R.id.select_album_pin);
|
||||
unpinButton = view.findViewById(R.id.select_album_unpin);
|
||||
downloadButton = view.findViewById(R.id.select_album_download);
|
||||
deleteButton = view.findViewById(R.id.select_album_delete);
|
||||
ImageView oreButton = view.findViewById(R.id.select_album_more);
|
||||
emptyView = view.findViewById(R.id.select_album_empty);
|
||||
|
||||
selectButton.setVisibility(View.GONE);
|
||||
playNextButton.setVisibility(View.GONE);
|
||||
playLastButton.setVisibility(View.GONE);
|
||||
oreButton.setVisibility(View.GONE);
|
||||
|
||||
playNowButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
playNow(getSelectedSongs(albumListView));
|
||||
}
|
||||
});
|
||||
|
||||
selectButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
selectAllOrNone();
|
||||
}
|
||||
});
|
||||
pinButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
downloadBackground(true);
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
unpinButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
unpin();
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
downloadButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
downloadBackground(false);
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
deleteButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
delete();
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(albumListView);
|
||||
FragmentTitle.Companion.setTitle(this, R.string.button_bar_bookmarks);
|
||||
|
||||
enableButtons();
|
||||
getBookmarks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void getBookmarks()
|
||||
{
|
||||
new LoadTask()
|
||||
{
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception
|
||||
{
|
||||
return Util.getSongsFromBookmarks(service.getBookmarks(getContext()));
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void playNow(List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
if (!getSelectedSongs(albumListView).isEmpty())
|
||||
{
|
||||
int position = songs.get(0).getBookmarkPosition();
|
||||
mediaPlayerController.getValue().restore(songs, 0, position, true, true);
|
||||
selectAll(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<MusicDirectory.Entry> getSelectedSongs(ListView albumListView)
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<>(10);
|
||||
|
||||
if (albumListView != null)
|
||||
{
|
||||
int count = albumListView.getCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (albumListView.isItemChecked(i))
|
||||
{
|
||||
songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
|
||||
private void selectAllOrNone()
|
||||
{
|
||||
boolean someUnselected = false;
|
||||
int count = albumListView.getCount();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!albumListView.isItemChecked(i) && albumListView.getItemAtPosition(i) instanceof MusicDirectory.Entry)
|
||||
{
|
||||
someUnselected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
selectAll(someUnselected, true);
|
||||
}
|
||||
|
||||
private void selectAll(boolean selected, boolean toast)
|
||||
{
|
||||
int count = albumListView.getCount();
|
||||
int selectedCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i);
|
||||
if (entry != null && !entry.isDirectory() && !entry.isVideo())
|
||||
{
|
||||
albumListView.setItemChecked(i, selected);
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Display toast: N tracks selected / N tracks unselected
|
||||
if (toast)
|
||||
{
|
||||
int toastResId = selected ? R.string.select_album_n_selected : R.string.select_album_n_unselected;
|
||||
Util.toast(getContext(), getString(toastResId, selectedCount));
|
||||
}
|
||||
|
||||
enableButtons();
|
||||
}
|
||||
|
||||
private void enableButtons()
|
||||
{
|
||||
List<MusicDirectory.Entry> selection = getSelectedSongs(albumListView);
|
||||
boolean enabled = !selection.isEmpty();
|
||||
boolean unpinEnabled = false;
|
||||
boolean deleteEnabled = false;
|
||||
|
||||
int pinnedCount = 0;
|
||||
|
||||
for (MusicDirectory.Entry song : selection)
|
||||
{
|
||||
DownloadFile downloadFile = mediaPlayerController.getValue().getDownloadFileForSong(song);
|
||||
if (downloadFile.isWorkDone())
|
||||
{
|
||||
deleteEnabled = true;
|
||||
}
|
||||
|
||||
if (downloadFile.isSaved())
|
||||
{
|
||||
pinnedCount++;
|
||||
unpinEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
playNowButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE);
|
||||
pinButton.setVisibility((enabled && !ActiveServerProvider.Companion.isOffline(getContext()) && selection.size() > pinnedCount) ? View.VISIBLE : View.GONE);
|
||||
unpinButton.setVisibility(enabled && unpinEnabled ? View.VISIBLE : View.GONE);
|
||||
downloadButton.setVisibility(enabled && !deleteEnabled && !ActiveServerProvider.Companion.isOffline(getContext()) ? View.VISIBLE : View.GONE);
|
||||
deleteButton.setVisibility(enabled && deleteEnabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void downloadBackground(final boolean save)
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs(albumListView);
|
||||
|
||||
if (songs.isEmpty())
|
||||
{
|
||||
selectAll(true, false);
|
||||
songs = getSelectedSongs(albumListView);
|
||||
}
|
||||
|
||||
downloadBackground(save, songs);
|
||||
}
|
||||
|
||||
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
Runnable onValid = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
|
||||
mediaPlayerController.getValue().downloadBackground(songs, save);
|
||||
|
||||
if (save)
|
||||
{
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onValid.run();
|
||||
}
|
||||
|
||||
private void delete()
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs(albumListView);
|
||||
|
||||
if (songs.isEmpty())
|
||||
{
|
||||
selectAll(true, false);
|
||||
songs = getSelectedSongs(albumListView);
|
||||
}
|
||||
|
||||
mediaPlayerController.getValue().delete(songs);
|
||||
}
|
||||
|
||||
private void unpin()
|
||||
{
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs(albumListView);
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size()));
|
||||
mediaPlayerController.getValue().unpin(songs);
|
||||
}
|
||||
|
||||
private abstract class LoadTask extends FragmentBackgroundTask<Pair<MusicDirectory, Boolean>>
|
||||
{
|
||||
public LoadTask()
|
||||
{
|
||||
super(BookmarksFragment.this.getActivity(), true, refreshAlbumListView, cancellationToken);
|
||||
}
|
||||
|
||||
protected abstract MusicDirectory load(MusicService service) throws Exception;
|
||||
|
||||
@Override
|
||||
protected Pair<MusicDirectory, Boolean> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
MusicDirectory dir = load(musicService);
|
||||
boolean valid = musicService.isLicenseValid(getContext());
|
||||
return new Pair<>(dir, valid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Pair<MusicDirectory, Boolean> result)
|
||||
{
|
||||
MusicDirectory musicDirectory = result.getFirst();
|
||||
List<MusicDirectory.Entry> entries = musicDirectory.getChildren();
|
||||
|
||||
int songCount = 0;
|
||||
for (MusicDirectory.Entry entry : entries)
|
||||
{
|
||||
if (!entry.isDirectory())
|
||||
{
|
||||
songCount++;
|
||||
}
|
||||
}
|
||||
|
||||
final int listSize = getArguments() == null? 0 : getArguments().getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0);
|
||||
|
||||
if (songCount > 0)
|
||||
{
|
||||
pinButton.setVisibility(View.VISIBLE);
|
||||
unpinButton.setVisibility(View.VISIBLE);
|
||||
downloadButton.setVisibility(View.VISIBLE);
|
||||
deleteButton.setVisibility(View.VISIBLE);
|
||||
playNowButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
pinButton.setVisibility(View.GONE);
|
||||
unpinButton.setVisibility(View.GONE);
|
||||
downloadButton.setVisibility(View.GONE);
|
||||
deleteButton.setVisibility(View.GONE);
|
||||
playNowButton.setVisibility(View.GONE);
|
||||
|
||||
if (listSize == 0 || result.getFirst().getChildren().size() < listSize)
|
||||
{
|
||||
albumButtons.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
enableButtons();
|
||||
|
||||
emptyView.setVisibility(entries.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
albumListView.setAdapter(new EntryAdapter(getContext(), imageLoader.getValue().getImageLoader(), entries, true));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.ChatMessage;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.ChatAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Provides online chat functionality
|
||||
*/
|
||||
public class ChatFragment extends Fragment {
|
||||
|
||||
private ListView chatListView;
|
||||
private EditText messageEditText;
|
||||
private ImageButton sendButton;
|
||||
private Timer timer;
|
||||
private volatile static Long lastChatMessageTime = (long) 0;
|
||||
private static final ArrayList<ChatMessage> messageList = new ArrayList<>();
|
||||
private CancellationToken cancellationToken;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
|
||||
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.chat, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
swipeRefresh = view.findViewById(R.id.chat_refresh);
|
||||
swipeRefresh.setEnabled(false);
|
||||
|
||||
cancellationToken = new CancellationToken();
|
||||
messageEditText = view.findViewById(R.id.chat_edittext);
|
||||
sendButton = view.findViewById(R.id.chat_send);
|
||||
|
||||
sendButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
chatListView = view.findViewById(R.id.chat_entries_list);
|
||||
chatListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
|
||||
chatListView.setStackFromBottom(true);
|
||||
|
||||
String serverName = activeServerProvider.getValue().getActiveServer().getName();
|
||||
String userName = activeServerProvider.getValue().getActiveServer().getUserName();
|
||||
String title = String.format("%s [%s@%s]", getResources().getString(R.string.button_bar_chat), userName, serverName);
|
||||
|
||||
FragmentTitle.Companion.setTitle(this, title);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
messageEditText.setImeActionLabel("Send", KeyEvent.KEYCODE_ENTER);
|
||||
|
||||
messageEditText.addTextChangedListener(new TextWatcher()
|
||||
{
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable)
|
||||
{
|
||||
sendButton.setEnabled(!Util.isNullOrWhiteSpace(editable.toString()));
|
||||
}
|
||||
});
|
||||
|
||||
messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
|
||||
{
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
{
|
||||
sendMessage();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
load();
|
||||
timerMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.chat, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
/*
|
||||
* Listen for option item selections so that we receive a notification
|
||||
* when the user requests a refresh by selecting the refresh action bar item.
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Check if user triggered a refresh:
|
||||
if (item.getItemId() == R.id.menu_refresh) {
|
||||
// Start the refresh background task.
|
||||
load();
|
||||
return true;
|
||||
}
|
||||
// User didn't trigger a refresh, let the superclass handle this action
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (!messageList.isEmpty())
|
||||
{
|
||||
ListAdapter chatAdapter = new ChatAdapter(getContext(), messageList);
|
||||
chatListView.setAdapter(chatAdapter);
|
||||
}
|
||||
|
||||
if (timer == null)
|
||||
{
|
||||
timerMethod();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
|
||||
if (timer != null)
|
||||
{
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void timerMethod()
|
||||
{
|
||||
int refreshInterval = Util.getChatRefreshInterval(getContext());
|
||||
|
||||
if (refreshInterval > 0)
|
||||
{
|
||||
timer = new Timer();
|
||||
|
||||
timer.schedule(new TimerTask()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
getActivity().runOnUiThread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
load();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, refreshInterval, refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage()
|
||||
{
|
||||
if (messageEditText != null)
|
||||
{
|
||||
final String message;
|
||||
Editable text = messageEditText.getText();
|
||||
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
message = text.toString();
|
||||
|
||||
if (!Util.isNullOrWhiteSpace(message))
|
||||
{
|
||||
messageEditText.setText("");
|
||||
|
||||
BackgroundTask<Void> task = new FragmentBackgroundTask<Void>(getActivity(), false, swipeRefresh, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
musicService.addChatMessage(message, getContext());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
load();
|
||||
}
|
||||
};
|
||||
|
||||
task.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void load()
|
||||
{
|
||||
BackgroundTask<List<ChatMessage>> task = new FragmentBackgroundTask<List<ChatMessage>>(getActivity(), false, swipeRefresh, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected List<ChatMessage> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
return musicService.getChatMessages(lastChatMessageTime, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<ChatMessage> result)
|
||||
{
|
||||
if (result != null && !result.isEmpty())
|
||||
{
|
||||
// Reset lastChatMessageTime if we have a newer message
|
||||
for (ChatMessage message : result)
|
||||
{
|
||||
if (message.getTime() > lastChatMessageTime)
|
||||
{
|
||||
lastChatMessageTime = message.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse results to show them on the bottom
|
||||
Collections.reverse(result);
|
||||
messageList.addAll(result);
|
||||
|
||||
ListAdapter chatAdapter = new ChatAdapter(getContext(), messageList);
|
||||
chatListView.setAdapter(chatAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error) {
|
||||
// Stop the timer in case of an error, otherwise it may repeat the error message forever
|
||||
if (timer != null)
|
||||
{
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
super.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
task.execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.media.audiofx.Equalizer;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.audiofx.EqualizerController;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Displays the Equalizer
|
||||
*/
|
||||
public class EqualizerFragment extends Fragment {
|
||||
|
||||
private static final int MENU_GROUP_PRESET = 100;
|
||||
|
||||
private final Map<Short, SeekBar> bars = new HashMap<>();
|
||||
private EqualizerController equalizerController;
|
||||
private Equalizer equalizer;
|
||||
private LinearLayout equalizerLayout;
|
||||
private View presetButton;
|
||||
private CheckBox enabledCheckBox;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.equalizer, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
FragmentTitle.Companion.setTitle(this, R.string.equalizer_label);
|
||||
equalizerLayout = view.findViewById(R.id.equalizer_layout);
|
||||
presetButton = view.findViewById(R.id.equalizer_preset);
|
||||
enabledCheckBox = view.findViewById(R.id.equalizer_enabled);
|
||||
|
||||
EqualizerController.get().observe(getViewLifecycleOwner(), new Observer<EqualizerController>() {
|
||||
@Override
|
||||
public void onChanged(EqualizerController controller) {
|
||||
if (controller != null) {
|
||||
Timber.d("EqualizerController Observer.onChanged received controller");
|
||||
equalizerController = controller;
|
||||
equalizer = controller.equalizer;
|
||||
setup();
|
||||
} else {
|
||||
Timber.d("EqualizerController Observer.onChanged has no controller");
|
||||
equalizerController = null;
|
||||
equalizer = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
if (equalizerController == null) return;
|
||||
equalizerController.saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NotNull ContextMenu menu, @NotNull View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
if (equalizer == null) return;
|
||||
|
||||
short currentPreset;
|
||||
try
|
||||
{
|
||||
currentPreset = equalizer.getCurrentPreset();
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
currentPreset = -1;
|
||||
}
|
||||
|
||||
for (short preset = 0; preset < equalizer.getNumberOfPresets(); preset++)
|
||||
{
|
||||
MenuItem menuItem = menu.add(MENU_GROUP_PRESET, preset, preset, equalizer.getPresetName(preset));
|
||||
if (preset == currentPreset)
|
||||
{
|
||||
menuItem.setChecked(true);
|
||||
}
|
||||
}
|
||||
menu.setGroupCheckable(MENU_GROUP_PRESET, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(@NotNull MenuItem menuItem)
|
||||
{
|
||||
if (equalizer == null) return true;
|
||||
try
|
||||
{
|
||||
short preset = (short) menuItem.getItemId();
|
||||
equalizer.usePreset(preset);
|
||||
updateBars();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog?
|
||||
Timber.i(ex, "An exception has occurred in EqualizerFragment onContextItemSelected");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setup()
|
||||
{
|
||||
initEqualizer();
|
||||
|
||||
registerForContextMenu(presetButton);
|
||||
presetButton.setOnClickListener(new View.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
presetButton.showContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
enabledCheckBox.setChecked(equalizer.getEnabled());
|
||||
enabledCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
|
||||
{
|
||||
setEqualizerEnabled(b);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setEqualizerEnabled(boolean enabled)
|
||||
{
|
||||
if (equalizer == null) return;
|
||||
equalizer.setEnabled(enabled);
|
||||
updateBars();
|
||||
}
|
||||
|
||||
private void updateBars()
|
||||
{
|
||||
if (equalizer == null) return;
|
||||
try
|
||||
{
|
||||
for (Map.Entry<Short, SeekBar> entry : bars.entrySet())
|
||||
{
|
||||
short band = entry.getKey();
|
||||
SeekBar bar = entry.getValue();
|
||||
bar.setEnabled(equalizer.getEnabled());
|
||||
short minEQLevel = equalizer.getBandLevelRange()[0];
|
||||
bar.setProgress(equalizer.getBandLevel(band) - minEQLevel);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog?
|
||||
Timber.i(ex, "An exception has occurred in EqualizerFragment updateBars");
|
||||
}
|
||||
}
|
||||
|
||||
private void initEqualizer()
|
||||
{
|
||||
if (equalizer == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
short[] bandLevelRange = equalizer.getBandLevelRange();
|
||||
short numberOfBands = equalizer.getNumberOfBands();
|
||||
|
||||
final short minEQLevel = bandLevelRange[0];
|
||||
final short maxEQLevel = bandLevelRange[1];
|
||||
|
||||
for (short i = 0; i < numberOfBands; i++)
|
||||
{
|
||||
final short band = i;
|
||||
|
||||
View bandBar = LayoutInflater.from(getContext()).inflate(R.layout.equalizer_bar, equalizerLayout, false);
|
||||
TextView freqTextView;
|
||||
|
||||
if (bandBar != null)
|
||||
{
|
||||
freqTextView = (TextView) bandBar.findViewById(R.id.equalizer_frequency);
|
||||
final TextView levelTextView = (TextView) bandBar.findViewById(R.id.equalizer_level);
|
||||
SeekBar bar = (SeekBar) bandBar.findViewById(R.id.equalizer_bar);
|
||||
|
||||
freqTextView.setText(String.format(Locale.getDefault(), "%d Hz", equalizer.getCenterFreq(band) / 1000));
|
||||
|
||||
bars.put(band, bar);
|
||||
bar.setMax(maxEQLevel - minEQLevel);
|
||||
short level = equalizer.getBandLevel(band);
|
||||
bar.setProgress(level - minEQLevel);
|
||||
bar.setEnabled(equalizer.getEnabled());
|
||||
updateLevelText(levelTextView, level);
|
||||
|
||||
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
||||
{
|
||||
short level = (short) (progress + minEQLevel);
|
||||
if (fromUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
equalizer.setBandLevel(band, level);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog?
|
||||
Timber.i(ex, "An exception has occurred in Equalizer onProgressChanged");
|
||||
}
|
||||
}
|
||||
updateLevelText(levelTextView, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
equalizerLayout.addView(bandBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//TODO: Show a dialog?
|
||||
Timber.i(ex, "An exception has occurred while initializing Equalizer");
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateLevelText(TextView levelTextView, short level)
|
||||
{
|
||||
if (levelTextView != null)
|
||||
{
|
||||
levelTextView.setText(String.format(Locale.getDefault(), "%s%d dB", level > 0 ? "+" : "", level / 100));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.Lyrics;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Displays the lyrics of a song
|
||||
*/
|
||||
public class LyricsFragment extends Fragment {
|
||||
|
||||
private TextView artistView;
|
||||
private TextView titleView;
|
||||
private TextView textView;
|
||||
private SwipeRefreshLayout swipe;
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.lyrics, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
cancellationToken = new CancellationToken();
|
||||
Timber.d("Lyrics set title");
|
||||
FragmentTitle.Companion.setTitle(this, R.string.download_menu_lyrics);
|
||||
|
||||
swipe = view.findViewById(R.id.lyrics_refresh);
|
||||
swipe.setEnabled(false);
|
||||
artistView = view.findViewById(R.id.lyrics_artist);
|
||||
titleView = view.findViewById(R.id.lyrics_title);
|
||||
textView = view.findViewById(R.id.lyrics_text);
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
BackgroundTask<Lyrics> task = new FragmentBackgroundTask<Lyrics>(getActivity(), true, swipe, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected Lyrics doInBackground() throws Throwable
|
||||
{
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments == null) return null;
|
||||
String artist = arguments.getString(Constants.INTENT_EXTRA_NAME_ARTIST);
|
||||
String title = arguments.getString(Constants.INTENT_EXTRA_NAME_TITLE);
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
return musicService.getLyrics(artist, title, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Lyrics result)
|
||||
{
|
||||
if (result != null && result.getArtist() != null)
|
||||
{
|
||||
artistView.setText(result.getArtist());
|
||||
titleView.setText(result.getTitle());
|
||||
textView.setText(result.getText());
|
||||
}
|
||||
else
|
||||
{
|
||||
artistView.setText(R.string.lyrics_nomatch);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,271 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.data.ServerSetting;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.MergeAdapter;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Displays the Main screen of Ultrasonic, where the music library can be browsed
|
||||
*/
|
||||
public class MainFragment extends Fragment {
|
||||
|
||||
private static boolean shouldUseId3;
|
||||
private static String lastActiveServerProperties;
|
||||
|
||||
private ListView list;
|
||||
|
||||
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.main, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
list = view.findViewById(R.id.main_list);
|
||||
setupMenuList(list);
|
||||
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
boolean shouldRestart = false;
|
||||
|
||||
boolean id3 = Util.getShouldUseId3Tags(MainFragment.this.getContext());
|
||||
String currentActiveServerProperties = getActiveServerProperties();
|
||||
|
||||
if (id3 != shouldUseId3)
|
||||
{
|
||||
shouldUseId3 = id3;
|
||||
shouldRestart = true;
|
||||
}
|
||||
|
||||
if (!currentActiveServerProperties.equals(lastActiveServerProperties))
|
||||
{
|
||||
lastActiveServerProperties = currentActiveServerProperties;
|
||||
shouldRestart = true;
|
||||
}
|
||||
|
||||
if (shouldRestart) {
|
||||
setupMenuList(list);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupMenuList(ListView list)
|
||||
{
|
||||
final View buttons = getLayoutInflater().inflate(R.layout.main_buttons, list, false);
|
||||
final View serverButton = buttons.findViewById(R.id.main_select_server);
|
||||
final TextView serverTextView = serverButton.findViewById(R.id.main_select_server_2);
|
||||
|
||||
lastActiveServerProperties = getActiveServerProperties();
|
||||
String name = activeServerProvider.getValue().getActiveServer().getName();
|
||||
|
||||
serverTextView.setText(name);
|
||||
|
||||
final View musicTitle = buttons.findViewById(R.id.main_music);
|
||||
final View artistsButton = buttons.findViewById(R.id.main_artists_button);
|
||||
final View albumsButton = buttons.findViewById(R.id.main_albums_button);
|
||||
final View genresButton = buttons.findViewById(R.id.main_genres_button);
|
||||
final View videosTitle = buttons.findViewById(R.id.main_videos_title);
|
||||
final View songsTitle = buttons.findViewById(R.id.main_songs);
|
||||
final View randomSongsButton = buttons.findViewById(R.id.main_songs_button);
|
||||
final View songsStarredButton = buttons.findViewById(R.id.main_songs_starred);
|
||||
final View albumsTitle = buttons.findViewById(R.id.main_albums);
|
||||
final View albumsNewestButton = buttons.findViewById(R.id.main_albums_newest);
|
||||
final View albumsRandomButton = buttons.findViewById(R.id.main_albums_random);
|
||||
final View albumsHighestButton = buttons.findViewById(R.id.main_albums_highest);
|
||||
final View albumsStarredButton = buttons.findViewById(R.id.main_albums_starred);
|
||||
final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent);
|
||||
final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent);
|
||||
final View albumsAlphaByNameButton = buttons.findViewById(R.id.main_albums_alphaByName);
|
||||
final View albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist);
|
||||
final View videosButton = buttons.findViewById(R.id.main_videos);
|
||||
|
||||
final MergeAdapter adapter = new MergeAdapter();
|
||||
adapter.addViews(Collections.singletonList(serverButton), true);
|
||||
|
||||
if (!ActiveServerProvider.Companion.isOffline(this.getContext()))
|
||||
{
|
||||
adapter.addView(musicTitle, false);
|
||||
adapter.addViews(asList(artistsButton, albumsButton, genresButton), true);
|
||||
adapter.addView(songsTitle, false);
|
||||
adapter.addViews(asList(randomSongsButton, songsStarredButton), true);
|
||||
adapter.addView(albumsTitle, false);
|
||||
|
||||
if (Util.getShouldUseId3Tags(MainFragment.this.getContext()))
|
||||
{
|
||||
shouldUseId3 = true;
|
||||
adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldUseId3 = false;
|
||||
adapter.addViews(asList(albumsNewestButton, albumsRecentButton, albumsFrequentButton, albumsHighestButton, albumsRandomButton, albumsStarredButton, albumsAlphaByNameButton, albumsAlphaByArtistButton), true);
|
||||
}
|
||||
|
||||
adapter.addView(videosTitle, false);
|
||||
adapter.addViews(Collections.singletonList(videosButton), true);
|
||||
}
|
||||
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (view == serverButton)
|
||||
{
|
||||
showServers();
|
||||
}
|
||||
else if (view == albumsNewestButton)
|
||||
{
|
||||
showAlbumList("newest", R.string.main_albums_newest);
|
||||
}
|
||||
else if (view == albumsRandomButton)
|
||||
{
|
||||
showAlbumList("random", R.string.main_albums_random);
|
||||
}
|
||||
else if (view == albumsHighestButton)
|
||||
{
|
||||
showAlbumList("highest", R.string.main_albums_highest);
|
||||
}
|
||||
else if (view == albumsRecentButton)
|
||||
{
|
||||
showAlbumList("recent", R.string.main_albums_recent);
|
||||
}
|
||||
else if (view == albumsFrequentButton)
|
||||
{
|
||||
showAlbumList("frequent", R.string.main_albums_frequent);
|
||||
}
|
||||
else if (view == albumsStarredButton)
|
||||
{
|
||||
showAlbumList(Constants.STARRED, R.string.main_albums_starred);
|
||||
}
|
||||
else if (view == albumsAlphaByNameButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName);
|
||||
}
|
||||
else if (view == albumsAlphaByArtistButton)
|
||||
{
|
||||
showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist);
|
||||
}
|
||||
else if (view == songsStarredButton)
|
||||
{
|
||||
showStarredSongs();
|
||||
}
|
||||
else if (view == artistsButton)
|
||||
{
|
||||
showArtists();
|
||||
}
|
||||
else if (view == albumsButton)
|
||||
{
|
||||
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title);
|
||||
}
|
||||
else if (view == randomSongsButton)
|
||||
{
|
||||
showRandomSongs();
|
||||
}
|
||||
else if (view == genresButton)
|
||||
{
|
||||
showGenres();
|
||||
}
|
||||
else if (view == videosButton)
|
||||
{
|
||||
showVideos();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getActiveServerProperties()
|
||||
{
|
||||
ServerSetting currentSetting = activeServerProvider.getValue().getActiveServer();
|
||||
return String.format("%s;%s;%s;%s;%s;%s", currentSetting.getUrl(), currentSetting.getUserName(),
|
||||
currentSetting.getPassword(), currentSetting.getAllowSelfSignedCertificate(),
|
||||
currentSetting.getLdapSupport(), currentSetting.getMinimumApiVersion());
|
||||
}
|
||||
|
||||
private void showAlbumList(final String type, final int title)
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums(getContext()));
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void showStarredSongs()
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_STARRED, 1);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void showRandomSongs()
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_RANDOM, 1);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(getContext()));
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void showArtists()
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, getContext().getResources().getString(R.string.main_artists_title));
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectArtistFragment, bundle);
|
||||
}
|
||||
|
||||
private void showGenres()
|
||||
{
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectGenre);
|
||||
}
|
||||
|
||||
private void showVideos()
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_VIDEOS, 1);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void showServers()
|
||||
{
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToServerSelector);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,209 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.service.DownloadFile;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.NowPlayingEventDistributor;
|
||||
import org.moire.ultrasonic.util.NowPlayingEventListener;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
|
||||
/**
|
||||
* Contains the mini-now playing information box displayed at the bottom of the screen
|
||||
*/
|
||||
public class NowPlayingFragment extends Fragment {
|
||||
|
||||
private static final int MIN_DISTANCE = 30;
|
||||
private float downX;
|
||||
private float downY;
|
||||
ImageView playButton;
|
||||
ImageView nowPlayingAlbumArtImage;
|
||||
TextView nowPlayingTrack;
|
||||
TextView nowPlayingArtist;
|
||||
|
||||
private final Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
private final Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class);
|
||||
private final Lazy<NowPlayingEventDistributor> nowPlayingEventDistributor = inject(NowPlayingEventDistributor.class);
|
||||
private NowPlayingEventListener nowPlayingEventListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.now_playing, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
playButton = (ImageView) view.findViewById(R.id.now_playing_control_play);
|
||||
nowPlayingAlbumArtImage = (ImageView) view.findViewById(R.id.now_playing_image);
|
||||
nowPlayingTrack = (TextView) view.findViewById(R.id.now_playing_trackname);
|
||||
nowPlayingArtist = (TextView) view.findViewById(R.id.now_playing_artist);
|
||||
|
||||
nowPlayingEventListener = new NowPlayingEventListener() {
|
||||
@Override
|
||||
public void onDismissNowPlaying() { }
|
||||
@Override
|
||||
public void onHideNowPlaying() { }
|
||||
@Override
|
||||
public void onShowNowPlaying() { Update(); }
|
||||
};
|
||||
|
||||
nowPlayingEventDistributor.getValue().subscribe(nowPlayingEventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
nowPlayingEventDistributor.getValue().unsubscribe(nowPlayingEventListener);
|
||||
}
|
||||
|
||||
private void Update() {
|
||||
try
|
||||
{
|
||||
PlayerState playerState = mediaPlayerControllerLazy.getValue().getPlayerState();
|
||||
if (playerState == PlayerState.PAUSED) {
|
||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_play));
|
||||
} else if (playerState == PlayerState.STARTED) {
|
||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_pause));
|
||||
}
|
||||
|
||||
DownloadFile file = mediaPlayerControllerLazy.getValue().getCurrentPlaying();
|
||||
if (file != null) {
|
||||
final MusicDirectory.Entry song = file.getSong();
|
||||
String title = song.getTitle();
|
||||
String artist = song.getArtist();
|
||||
|
||||
imageLoader.getValue().getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(getContext()), false, true);
|
||||
nowPlayingTrack.setText(title);
|
||||
nowPlayingArtist.setText(artist);
|
||||
|
||||
nowPlayingAlbumArtImage.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
if (Util.getShouldUseId3Tags(getContext())) {
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true);
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, song.getAlbumId());
|
||||
} else {
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false);
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, song.getParent());
|
||||
}
|
||||
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getView().setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
return handleOnTouch(event);
|
||||
}
|
||||
});
|
||||
|
||||
// This empty onClickListener is necessary for the onTouchListener to work
|
||||
getView().setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
}
|
||||
});
|
||||
|
||||
playButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mediaPlayerControllerLazy.getValue().togglePlayPause();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception x) {
|
||||
Timber.w(x, "Failed to get notification cover art");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleOnTouch(MotionEvent event) {
|
||||
switch (event.getAction())
|
||||
{
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
{
|
||||
downX = event.getX();
|
||||
downY = event.getY();
|
||||
return false;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
{
|
||||
float upX = event.getX();
|
||||
float upY = event.getY();
|
||||
|
||||
float deltaX = downX - upX;
|
||||
float deltaY = downY - upY;
|
||||
|
||||
if (Math.abs(deltaX) > MIN_DISTANCE)
|
||||
{
|
||||
// left or right
|
||||
if (deltaX < 0)
|
||||
{
|
||||
mediaPlayerControllerLazy.getValue().previous();
|
||||
return false;
|
||||
}
|
||||
if (deltaX > 0)
|
||||
{
|
||||
mediaPlayerControllerLazy.getValue().next();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (Math.abs(deltaY) > MIN_DISTANCE)
|
||||
{
|
||||
if (deltaY < 0)
|
||||
{
|
||||
nowPlayingEventDistributor.getValue().raiseNowPlayingDismissedEvent();
|
||||
return false;
|
||||
}
|
||||
if (deltaY > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Navigation.findNavController(getActivity(), R.id.nav_host_fragment).navigate(R.id.playerFragment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,342 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.Playlist;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.service.OfflineException;
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CacheCleaner;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.LoadingTask;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.PlaylistAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Displays the playlists stored on the server
|
||||
*/
|
||||
public class PlaylistsFragment extends Fragment {
|
||||
|
||||
private SwipeRefreshLayout refreshPlaylistsListView;
|
||||
private ListView playlistsListView;
|
||||
private View emptyTextView;
|
||||
private PlaylistAdapter playlistAdapter;
|
||||
|
||||
private final Lazy<DownloadHandler> downloadHandler = inject(DownloadHandler.class);
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.select_playlist, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
cancellationToken = new CancellationToken();
|
||||
|
||||
refreshPlaylistsListView = view.findViewById(R.id.select_playlist_refresh);
|
||||
playlistsListView = view.findViewById(R.id.select_playlist_list);
|
||||
|
||||
refreshPlaylistsListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
load(true);
|
||||
}
|
||||
});
|
||||
|
||||
emptyTextView = view.findViewById(R.id.select_playlist_empty);
|
||||
playlistsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Playlist playlist = (Playlist) parent.getItemAtPosition(position);
|
||||
|
||||
if (playlist == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
}
|
||||
});
|
||||
registerForContextMenu(playlistsListView);
|
||||
FragmentTitle.Companion.setTitle(this, R.string.playlist_label);
|
||||
|
||||
load(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void load(final boolean refresh)
|
||||
{
|
||||
BackgroundTask<List<Playlist>> task = new FragmentBackgroundTask<List<Playlist>>(getActivity(), true, refreshPlaylistsListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected List<Playlist> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
List<Playlist> playlists = musicService.getPlaylists(refresh, getContext());
|
||||
|
||||
if (!ActiveServerProvider.Companion.isOffline(getContext()))
|
||||
new CacheCleaner(getContext()).cleanPlaylists(playlists);
|
||||
return playlists;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<Playlist> result)
|
||||
{
|
||||
playlistsListView.setAdapter(playlistAdapter = new PlaylistAdapter(getContext(), result));
|
||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NotNull ContextMenu menu, @NotNull View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
if (ActiveServerProvider.Companion.isOffline(getContext())) inflater.inflate(R.menu.select_playlist_context_offline, menu);
|
||||
else inflater.inflate(R.menu.select_playlist_context, menu);
|
||||
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
|
||||
|
||||
if (downloadMenuItem != null)
|
||||
{
|
||||
downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
if (info == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position);
|
||||
if (playlist == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Bundle bundle;
|
||||
int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.playlist_menu_pin) {
|
||||
downloadHandler.getValue().downloadPlaylist(this, playlist.getId(), playlist.getName(), true, true, false, false, true, false, false);
|
||||
} else if (itemId == R.id.playlist_menu_unpin) {
|
||||
downloadHandler.getValue().downloadPlaylist(this, playlist.getId(), playlist.getName(), false, false, false, false, true, false, true);
|
||||
} else if (itemId == R.id.playlist_menu_download) {
|
||||
downloadHandler.getValue().downloadPlaylist(this, playlist.getId(), playlist.getName(), false, false, false, false, true, false, false);
|
||||
} else if (itemId == R.id.playlist_menu_play_now) {
|
||||
bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
} else if (itemId == R.id.playlist_menu_play_shuffled) {
|
||||
bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
|
||||
} else if (itemId == R.id.playlist_menu_delete) {
|
||||
deletePlaylist(playlist);
|
||||
} else if (itemId == R.id.playlist_info) {
|
||||
displayPlaylistInfo(playlist);
|
||||
} else if (itemId == R.id.playlist_update_info) {
|
||||
updatePlaylistInfo(playlist);
|
||||
} else {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void deletePlaylist(final Playlist playlist)
|
||||
{
|
||||
new AlertDialog.Builder(getContext()).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, playlist.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(getActivity(), refreshPlaylistsListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
musicService.deletePlaylist(playlist.getId(), getContext());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
playlistAdapter.remove(playlist);
|
||||
playlistAdapter.notifyDataSetChanged();
|
||||
Util.toast(getContext(), getResources().getString(R.string.menu_deleted_playlist, playlist.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(getContext(), msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
}).setNegativeButton(R.string.common_cancel, null).show();
|
||||
}
|
||||
|
||||
private void displayPlaylistInfo(final Playlist playlist)
|
||||
{
|
||||
final TextView textView = new TextView(getContext());
|
||||
textView.setPadding(5, 5, 5, 5);
|
||||
|
||||
final Spannable message = new SpannableString("Owner: " + playlist.getOwner() + "\nComments: " +
|
||||
((playlist.getComment() == null) ? "" : playlist.getComment()) +
|
||||
"\nSong Count: " + playlist.getSongCount() +
|
||||
((playlist.getPublic() == null) ? "" : ("\nPublic: " + playlist.getPublic()) + ((playlist.getCreated() == null) ? "" : ("\nCreation Date: " + playlist.getCreated().replace('T', ' ')))));
|
||||
|
||||
Linkify.addLinks(message, Linkify.WEB_URLS);
|
||||
textView.setText(message);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
new AlertDialog.Builder(getContext()).setTitle(playlist.getName()).setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show();
|
||||
}
|
||||
|
||||
private void updatePlaylistInfo(final Playlist playlist)
|
||||
{
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.update_playlist, null);
|
||||
|
||||
if (dialogView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final EditText nameBox = dialogView.findViewById(R.id.get_playlist_name);
|
||||
final EditText commentBox = dialogView.findViewById(R.id.get_playlist_comment);
|
||||
final CheckBox publicBox = dialogView.findViewById(R.id.get_playlist_public);
|
||||
|
||||
nameBox.setText(playlist.getName());
|
||||
commentBox.setText(playlist.getComment());
|
||||
Boolean pub = playlist.getPublic();
|
||||
|
||||
if (pub == null)
|
||||
{
|
||||
publicBox.setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
publicBox.setChecked(pub);
|
||||
}
|
||||
|
||||
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
|
||||
|
||||
alertDialog.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alertDialog.setTitle(R.string.playlist_update_info);
|
||||
alertDialog.setView(dialogView);
|
||||
alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(getActivity(), refreshPlaylistsListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
Editable nameBoxText = nameBox.getText();
|
||||
Editable commentBoxText = commentBox.getText();
|
||||
String name = nameBoxText != null ? nameBoxText.toString() : null;
|
||||
String comment = commentBoxText != null ? commentBoxText.toString() : null;
|
||||
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
musicService.updatePlaylist(playlist.getId(), name, comment, publicBox.isChecked(), getContext());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
load(true);
|
||||
Util.toast(getContext(), getResources().getString(R.string.playlist_updated_info, playlist.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, playlist.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(getContext(), msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
});
|
||||
alertDialog.setNegativeButton(R.string.common_cancel, null);
|
||||
alertDialog.show();
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.PodcastsChannel;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.PodcastsChannelsAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays the podcasts available on the server
|
||||
*/
|
||||
public class PodcastFragment extends Fragment {
|
||||
|
||||
private View emptyTextView;
|
||||
ListView channelItemsListView = null;
|
||||
private CancellationToken cancellationToken;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.podcasts, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
cancellationToken = new CancellationToken();
|
||||
swipeRefresh = view.findViewById(R.id.podcasts_refresh);
|
||||
swipeRefresh.setEnabled(false);
|
||||
|
||||
FragmentTitle.Companion.setTitle(this, R.string.podcasts_label);
|
||||
|
||||
emptyTextView = view.findViewById(R.id.select_podcasts_empty);
|
||||
channelItemsListView = view.findViewById(R.id.podcasts_channels_items_list);
|
||||
channelItemsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
PodcastsChannel pc = (PodcastsChannel) parent.getItemAtPosition(position);
|
||||
if (pc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_PODCAST_CHANNEL_ID, pc.getId());
|
||||
Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle);
|
||||
}
|
||||
});
|
||||
|
||||
load(view.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void load(final Context context)
|
||||
{
|
||||
BackgroundTask<List<PodcastsChannel>> task = new FragmentBackgroundTask<List<PodcastsChannel>>(getActivity(), true, swipeRefresh, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected List<PodcastsChannel> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
return musicService.getPodcastsChannels(false, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<PodcastsChannel> result)
|
||||
{
|
||||
channelItemsListView.setAdapter(new PodcastsChannelsAdapter(context, result));
|
||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,593 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.Artist;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.SearchCriteria;
|
||||
import org.moire.ultrasonic.domain.SearchResult;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler;
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
|
||||
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker;
|
||||
import org.moire.ultrasonic.subsonic.ShareHandler;
|
||||
import org.moire.ultrasonic.subsonic.VideoPlayer;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.MergeAdapter;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.ArtistAdapter;
|
||||
import org.moire.ultrasonic.view.EntryAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Initiates a search on the media library and displays the results
|
||||
*/
|
||||
public class SearchFragment extends Fragment {
|
||||
|
||||
private static int DEFAULT_ARTISTS;
|
||||
private static int DEFAULT_ALBUMS;
|
||||
private static int DEFAULT_SONGS;
|
||||
|
||||
private ListView list;
|
||||
|
||||
private View artistsHeading;
|
||||
private View albumsHeading;
|
||||
private View songsHeading;
|
||||
private TextView notFound;
|
||||
private View moreArtistsButton;
|
||||
private View moreAlbumsButton;
|
||||
private View moreSongsButton;
|
||||
private SearchResult searchResult;
|
||||
private MergeAdapter mergeAdapter;
|
||||
private ArtistAdapter artistAdapter;
|
||||
private ListAdapter moreArtistsAdapter;
|
||||
private EntryAdapter albumAdapter;
|
||||
private ListAdapter moreAlbumsAdapter;
|
||||
private ListAdapter moreSongsAdapter;
|
||||
private EntryAdapter songAdapter;
|
||||
private SwipeRefreshLayout searchRefresh;
|
||||
|
||||
private final Lazy<VideoPlayer> videoPlayer = inject(VideoPlayer.class);
|
||||
private final Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
private final Lazy<ImageLoaderProvider> imageLoaderProvider = inject(ImageLoaderProvider.class);
|
||||
private final Lazy<DownloadHandler> downloadHandler = inject(DownloadHandler.class);
|
||||
private final Lazy<ShareHandler> shareHandler = inject(ShareHandler.class);
|
||||
private final Lazy<NetworkAndStorageChecker> networkAndStorageChecker = inject(NetworkAndStorageChecker.class);
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.search, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
cancellationToken = new CancellationToken();
|
||||
|
||||
FragmentTitle.Companion.setTitle(this, R.string.search_title);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
DEFAULT_ARTISTS = Util.getDefaultArtists(getContext());
|
||||
DEFAULT_ALBUMS = Util.getDefaultAlbums(getContext());
|
||||
DEFAULT_SONGS = Util.getDefaultSongs(getContext());
|
||||
|
||||
View buttons = LayoutInflater.from(getContext()).inflate(R.layout.search_buttons, list, false);
|
||||
|
||||
if (buttons != null)
|
||||
{
|
||||
artistsHeading = buttons.findViewById(R.id.search_artists);
|
||||
albumsHeading = buttons.findViewById(R.id.search_albums);
|
||||
songsHeading = buttons.findViewById(R.id.search_songs);
|
||||
notFound = buttons.findViewById(R.id.search_not_found);
|
||||
moreArtistsButton = buttons.findViewById(R.id.search_more_artists);
|
||||
moreAlbumsButton = buttons.findViewById(R.id.search_more_albums);
|
||||
moreSongsButton = buttons.findViewById(R.id.search_more_songs);
|
||||
}
|
||||
|
||||
list = view.findViewById(R.id.search_list);
|
||||
searchRefresh = view.findViewById(R.id.search_entries_refresh);
|
||||
searchRefresh.setEnabled(false); // TODO: It should be enabled if it is a good feature to refresh search results
|
||||
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
|
||||
{
|
||||
if (view == moreArtistsButton)
|
||||
{
|
||||
expandArtists();
|
||||
}
|
||||
else if (view == moreAlbumsButton)
|
||||
{
|
||||
expandAlbums();
|
||||
}
|
||||
else if (view == moreSongsButton)
|
||||
{
|
||||
expandSongs();
|
||||
}
|
||||
else
|
||||
{
|
||||
Object item = parent.getItemAtPosition(position);
|
||||
if (item instanceof Artist)
|
||||
{
|
||||
onArtistSelected((Artist) item);
|
||||
}
|
||||
else if (item instanceof MusicDirectory.Entry)
|
||||
{
|
||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) item;
|
||||
if (entry.isDirectory())
|
||||
{
|
||||
onAlbumSelected(entry, false);
|
||||
}
|
||||
else if (entry.isVideo())
|
||||
{
|
||||
onVideoSelected(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
onSongSelected(entry, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(list);
|
||||
|
||||
// Fragment was started with a query (e.g. from voice search), try to execute search right away
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
String query = arguments.getString(Constants.INTENT_EXTRA_NAME_QUERY);
|
||||
boolean autoPlay = arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
|
||||
|
||||
if (query != null) {
|
||||
mergeAdapter = new MergeAdapter();
|
||||
list.setAdapter(mergeAdapter);
|
||||
search(query, autoPlay);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fragment was started from the Menu, create empty list
|
||||
populateList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) return;
|
||||
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
|
||||
|
||||
inflater.inflate(R.menu.search, menu);
|
||||
MenuItem searchItem = menu.findItem(R.id.search_item);
|
||||
final SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName()));
|
||||
|
||||
Bundle arguments = getArguments();
|
||||
final boolean autoPlay = arguments != null && arguments.getBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, false);
|
||||
String query = arguments == null? null : arguments.getString(Constants.INTENT_EXTRA_NAME_QUERY);
|
||||
// If started with a query, enter it to the searchView
|
||||
if (query != null) {
|
||||
searchView.setQuery(query, false);
|
||||
searchView.clearFocus();
|
||||
}
|
||||
|
||||
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
|
||||
@Override
|
||||
public boolean onSuggestionSelect(int position) { return true; }
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionClick(int position) {
|
||||
Timber.d("onSuggestionClick: %d", position);
|
||||
Cursor cursor= searchView.getSuggestionsAdapter().getCursor();
|
||||
cursor.moveToPosition(position);
|
||||
String suggestion = cursor.getString(2); // TODO: Try to do something with this magic const -- 2 is the index of col containing suggestion name.
|
||||
searchView.setQuery(suggestion,true);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
Timber.d("onQueryTextSubmit: %s", query);
|
||||
mergeAdapter = new MergeAdapter();
|
||||
list.setAdapter(mergeAdapter);
|
||||
searchView.clearFocus();
|
||||
search(query, autoPlay);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) { return true; }
|
||||
});
|
||||
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchItem.expandActionView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NotNull ContextMenu menu, @NotNull View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
if (getActivity() == null) return;
|
||||
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
Object selectedItem = list.getItemAtPosition(info.position);
|
||||
|
||||
boolean isArtist = selectedItem instanceof Artist;
|
||||
boolean isAlbum = selectedItem instanceof MusicDirectory.Entry && ((MusicDirectory.Entry) selectedItem).isDirectory();
|
||||
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
if (!isArtist && !isAlbum)
|
||||
{
|
||||
inflater.inflate(R.menu.select_song_context, menu);
|
||||
}
|
||||
else
|
||||
{
|
||||
inflater.inflate(R.menu.select_album_context, menu);
|
||||
}
|
||||
|
||||
MenuItem shareButton = menu.findItem(R.id.menu_item_share);
|
||||
MenuItem downloadMenuItem = menu.findItem(R.id.album_menu_download);
|
||||
|
||||
if (downloadMenuItem != null)
|
||||
{
|
||||
downloadMenuItem.setVisible(!ActiveServerProvider.Companion.isOffline(getContext()));
|
||||
}
|
||||
|
||||
if (ActiveServerProvider.Companion.isOffline(getContext()) || isArtist)
|
||||
{
|
||||
if (shareButton != null)
|
||||
{
|
||||
shareButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Object selectedItem = list.getItemAtPosition(info.position);
|
||||
|
||||
Artist artist = selectedItem instanceof Artist ? (Artist) selectedItem : null;
|
||||
MusicDirectory.Entry entry = selectedItem instanceof MusicDirectory.Entry ? (MusicDirectory.Entry) selectedItem : null;
|
||||
|
||||
String entryId = null;
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
entryId = entry.getId();
|
||||
}
|
||||
|
||||
String id = artist != null ? artist.getId() : entryId;
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<>(1);
|
||||
|
||||
int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.album_menu_play_now) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, false, true, false, false, false, false, false);
|
||||
} else if (itemId == R.id.album_menu_play_next) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, true, false, true, false, true, false, false);
|
||||
} else if (itemId == R.id.album_menu_play_last) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, true, false, false, false, false, false, false);
|
||||
} else if (itemId == R.id.album_menu_pin) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, true, true, false, false, false, false, false, false);
|
||||
} else if (itemId == R.id.album_menu_unpin) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, false, false, false, false, false, true, false);
|
||||
} else if (itemId == R.id.album_menu_download) {
|
||||
downloadHandler.getValue().downloadRecursively(this, id, false, false, false, false, true, false, false, false);
|
||||
} else if (itemId == R.id.song_menu_play_now) {
|
||||
if (entry != null) {
|
||||
songs = new ArrayList<>(1);
|
||||
songs.add(entry);
|
||||
downloadHandler.getValue().download(this, false, false, true, false, false, songs);
|
||||
}
|
||||
} else if (itemId == R.id.song_menu_play_next) {
|
||||
if (entry != null) {
|
||||
songs = new ArrayList<>(1);
|
||||
songs.add(entry);
|
||||
downloadHandler.getValue().download(this, true, false, false, true, false, songs);
|
||||
}
|
||||
} else if (itemId == R.id.song_menu_play_last) {
|
||||
if (entry != null) {
|
||||
songs = new ArrayList<>(1);
|
||||
songs.add(entry);
|
||||
downloadHandler.getValue().download(this, true, false, false, false, false, songs);
|
||||
}
|
||||
} else if (itemId == R.id.song_menu_pin) {
|
||||
if (entry != null) {
|
||||
songs.add(entry);
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_pinned, songs.size(), songs.size()));
|
||||
downloadBackground(true, songs);
|
||||
}
|
||||
} else if (itemId == R.id.song_menu_download) {
|
||||
if (entry != null) {
|
||||
songs.add(entry);
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_downloaded, songs.size(), songs.size()));
|
||||
downloadBackground(false, songs);
|
||||
}
|
||||
} else if (itemId == R.id.song_menu_unpin) {
|
||||
if (entry != null) {
|
||||
songs.add(entry);
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size()));
|
||||
mediaPlayerControllerLazy.getValue().unpin(songs);
|
||||
}
|
||||
} else if (itemId == R.id.menu_item_share) {
|
||||
if (entry != null) {
|
||||
songs = new ArrayList<>(1);
|
||||
songs.add(entry);
|
||||
shareHandler.getValue().createShare(this, songs, searchRefresh, cancellationToken);
|
||||
}
|
||||
|
||||
return super.onContextItemSelected(menuItem);
|
||||
} else {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
Runnable onValid = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
|
||||
mediaPlayerControllerLazy.getValue().downloadBackground(songs, save);
|
||||
}
|
||||
};
|
||||
|
||||
onValid.run();
|
||||
}
|
||||
|
||||
private void search(final String query, final boolean autoplay)
|
||||
{
|
||||
final int maxArtists = Util.getMaxArtists(getContext());
|
||||
final int maxAlbums = Util.getMaxAlbums(getContext());
|
||||
final int maxSongs = Util.getMaxSongs(getContext());
|
||||
|
||||
BackgroundTask<SearchResult> task = new FragmentBackgroundTask<SearchResult>(getActivity(), true, searchRefresh, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected SearchResult doInBackground() throws Throwable
|
||||
{
|
||||
SearchCriteria criteria = new SearchCriteria(query, maxArtists, maxAlbums, maxSongs);
|
||||
MusicService service = MusicServiceFactory.getMusicService(getContext());
|
||||
return service.search(criteria, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(SearchResult result)
|
||||
{
|
||||
searchResult = result;
|
||||
|
||||
populateList();
|
||||
|
||||
if (autoplay)
|
||||
{
|
||||
autoplay();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
private void populateList()
|
||||
{
|
||||
mergeAdapter = new MergeAdapter();
|
||||
|
||||
if (searchResult != null)
|
||||
{
|
||||
List<Artist> artists = searchResult.getArtists();
|
||||
if (!artists.isEmpty())
|
||||
{
|
||||
mergeAdapter.addView(artistsHeading);
|
||||
List<Artist> displayedArtists = new ArrayList<>(artists.subList(0, Math.min(DEFAULT_ARTISTS, artists.size())));
|
||||
artistAdapter = new ArtistAdapter(getContext(), displayedArtists);
|
||||
mergeAdapter.addAdapter(artistAdapter);
|
||||
if (artists.size() > DEFAULT_ARTISTS)
|
||||
{
|
||||
moreArtistsAdapter = mergeAdapter.addView(moreArtistsButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<MusicDirectory.Entry> albums = searchResult.getAlbums();
|
||||
if (!albums.isEmpty())
|
||||
{
|
||||
mergeAdapter.addView(albumsHeading);
|
||||
List<MusicDirectory.Entry> displayedAlbums = new ArrayList<>(albums.subList(0, Math.min(DEFAULT_ALBUMS, albums.size())));
|
||||
albumAdapter = new EntryAdapter(getContext(), imageLoaderProvider.getValue().getImageLoader(), displayedAlbums, false);
|
||||
mergeAdapter.addAdapter(albumAdapter);
|
||||
if (albums.size() > DEFAULT_ALBUMS)
|
||||
{
|
||||
moreAlbumsAdapter = mergeAdapter.addView(moreAlbumsButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<MusicDirectory.Entry> songs = searchResult.getSongs();
|
||||
if (!songs.isEmpty())
|
||||
{
|
||||
mergeAdapter.addView(songsHeading);
|
||||
List<MusicDirectory.Entry> displayedSongs = new ArrayList<>(songs.subList(0, Math.min(DEFAULT_SONGS, songs.size())));
|
||||
songAdapter = new EntryAdapter(getContext(), imageLoaderProvider.getValue().getImageLoader(), displayedSongs, false);
|
||||
mergeAdapter.addAdapter(songAdapter);
|
||||
if (songs.size() > DEFAULT_SONGS)
|
||||
{
|
||||
moreSongsAdapter = mergeAdapter.addView(moreSongsButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
boolean empty = searchResult.getArtists().isEmpty() && searchResult.getAlbums().isEmpty() && searchResult.getSongs().isEmpty();
|
||||
if (empty) mergeAdapter.addView(notFound, false);
|
||||
}
|
||||
|
||||
list.setAdapter(mergeAdapter);
|
||||
}
|
||||
|
||||
private void expandArtists()
|
||||
{
|
||||
artistAdapter.clear();
|
||||
|
||||
for (Artist artist : searchResult.getArtists())
|
||||
{
|
||||
artistAdapter.add(artist);
|
||||
}
|
||||
|
||||
artistAdapter.notifyDataSetChanged();
|
||||
mergeAdapter.removeAdapter(moreArtistsAdapter);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void expandAlbums()
|
||||
{
|
||||
albumAdapter.clear();
|
||||
|
||||
for (MusicDirectory.Entry album : searchResult.getAlbums())
|
||||
{
|
||||
albumAdapter.add(album);
|
||||
}
|
||||
|
||||
albumAdapter.notifyDataSetChanged();
|
||||
mergeAdapter.removeAdapter(moreAlbumsAdapter);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void expandSongs()
|
||||
{
|
||||
songAdapter.clear();
|
||||
|
||||
for (MusicDirectory.Entry song : searchResult.getSongs())
|
||||
{
|
||||
songAdapter.add(song);
|
||||
}
|
||||
|
||||
songAdapter.notifyDataSetChanged();
|
||||
mergeAdapter.removeAdapter(moreSongsAdapter);
|
||||
mergeAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void onArtistSelected(Artist artist)
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, artist.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, artist.getId());
|
||||
Navigation.findNavController(getView()).navigate(R.id.searchToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void onAlbumSelected(MusicDirectory.Entry album, boolean autoplay)
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, album.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, album.getTitle());
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, album.isDirectory());
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoplay);
|
||||
Navigation.findNavController(getView()).navigate(R.id.searchToSelectAlbum, bundle);
|
||||
}
|
||||
|
||||
private void onSongSelected(MusicDirectory.Entry song, boolean append)
|
||||
{
|
||||
MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
|
||||
if (mediaPlayerController != null)
|
||||
{
|
||||
if (!append)
|
||||
{
|
||||
mediaPlayerController.clear();
|
||||
}
|
||||
|
||||
mediaPlayerController.download(Collections.singletonList(song), false, false, false, false, false);
|
||||
|
||||
if (true)
|
||||
{
|
||||
mediaPlayerController.play(mediaPlayerController.getPlaylistSize() - 1);
|
||||
}
|
||||
|
||||
Util.toast(getContext(), getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void onVideoSelected(MusicDirectory.Entry entry)
|
||||
{
|
||||
videoPlayer.getValue().playVideo(entry);
|
||||
}
|
||||
|
||||
private void autoplay()
|
||||
{
|
||||
if (!searchResult.getSongs().isEmpty())
|
||||
{
|
||||
onSongSelected(searchResult.getSongs().get(0), false);
|
||||
}
|
||||
else if (!searchResult.getAlbums().isEmpty())
|
||||
{
|
||||
onAlbumSelected(searchResult.getAlbums().get(0), true);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,134 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.Genre;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.GenreAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Displays the available genres in the media library
|
||||
*/
|
||||
public class SelectGenreFragment extends Fragment {
|
||||
|
||||
private SwipeRefreshLayout refreshGenreListView;
|
||||
private ListView genreListView;
|
||||
private View emptyView;
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.select_genre, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
cancellationToken = new CancellationToken();
|
||||
refreshGenreListView = view.findViewById(R.id.select_genre_refresh);
|
||||
genreListView = view.findViewById(R.id.select_genre_list);
|
||||
|
||||
refreshGenreListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
load(true);
|
||||
}
|
||||
});
|
||||
|
||||
genreListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Genre genre = (Genre) parent.getItemAtPosition(position);
|
||||
|
||||
if (genre != null)
|
||||
{
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_GENRE_NAME, genre.getName());
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxSongs(getContext()));
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
emptyView = view.findViewById(R.id.select_genre_empty);
|
||||
registerForContextMenu(genreListView);
|
||||
|
||||
FragmentTitle.Companion.setTitle(this, R.string.main_genres_title);
|
||||
load(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void load(final boolean refresh)
|
||||
{
|
||||
BackgroundTask<List<Genre>> task = new FragmentBackgroundTask<List<Genre>>(getActivity(), true, refreshGenreListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected List<Genre> doInBackground()
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
|
||||
List<Genre> genres = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
genres = musicService.getGenres(refresh, getContext());
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
Timber.e(x, "Failed to load genres");
|
||||
}
|
||||
|
||||
return genres;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<Genre> result)
|
||||
{
|
||||
emptyView.setVisibility(result == null || result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
genreListView.setAdapter(new GenreAdapter(getContext(), result));
|
||||
}
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
}
|
@ -2,22 +2,28 @@ package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.*;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.preference.CheckBoxPreference;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import timber.log.Timber;
|
||||
import android.view.View;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.koin.java.KoinJavaComponent;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.ServerSelectorActivity;
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import org.moire.ultrasonic.featureflags.Feature;
|
||||
import org.moire.ultrasonic.featureflags.FeatureStorage;
|
||||
import org.moire.ultrasonic.filepicker.FilePickerDialog;
|
||||
@ -26,6 +32,7 @@ import org.moire.ultrasonic.log.FileLoggerTree;
|
||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
|
||||
import org.moire.ultrasonic.service.Consumer;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
|
||||
import org.moire.ultrasonic.util.*;
|
||||
|
||||
import java.io.File;
|
||||
@ -33,14 +40,15 @@ import java.io.File;
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
import static org.moire.ultrasonic.activity.ServerSelectorActivity.SERVER_SELECTOR_MANAGE_MODE;
|
||||
import static org.moire.ultrasonic.fragment.ServerSelectorFragment.SERVER_SELECTOR_MANAGE_MODE;
|
||||
|
||||
/**
|
||||
* Shows main app settings.
|
||||
*/
|
||||
public class SettingsFragment extends PreferenceFragment
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private Preference addServerPreference;
|
||||
private ListPreference theme;
|
||||
private ListPreference videoPlayer;
|
||||
private ListPreference maxBitrateWifi;
|
||||
@ -69,61 +77,68 @@ public class SettingsFragment extends PreferenceFragment
|
||||
private EditTextPreference sharingDefaultDescription;
|
||||
private EditTextPreference sharingDefaultGreeting;
|
||||
private TimeSpanPreference sharingDefaultExpiration;
|
||||
private PreferenceCategory serversCategory;
|
||||
private Preference resumeOnBluetoothDevice;
|
||||
private Preference pauseOnBluetoothDevice;
|
||||
private CheckBoxPreference debugLogToFile;
|
||||
|
||||
private SharedPreferences settings;
|
||||
|
||||
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
private final Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
private final Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class);
|
||||
private final Lazy<PermissionUtil> permissionUtil = inject(PermissionUtil.class);
|
||||
private final Lazy<ThemeChangedEventDistributor> themeChangedEventDistributor = inject(ThemeChangedEventDistributor.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.settings);
|
||||
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
}
|
||||
|
||||
theme = (ListPreference) findPreference(Constants.PREFERENCES_KEY_THEME);
|
||||
videoPlayer = (ListPreference) findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER);
|
||||
maxBitrateWifi = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI);
|
||||
maxBitrateMobile = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE);
|
||||
cacheSize = (ListPreference) findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE);
|
||||
@Override
|
||||
public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
FragmentTitle.Companion.setTitle(this, R.string.menu_settings);
|
||||
|
||||
addServerPreference = findPreference(Constants.PREFERENCES_KEY_SERVERS_EDIT);
|
||||
theme = findPreference(Constants.PREFERENCES_KEY_THEME);
|
||||
videoPlayer = findPreference(Constants.PREFERENCES_KEY_VIDEO_PLAYER);
|
||||
maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI);
|
||||
maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE);
|
||||
cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE);
|
||||
cacheLocation = findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION);
|
||||
preloadCount = (ListPreference) findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT);
|
||||
bufferLength = (ListPreference) findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH);
|
||||
incrementTime = (ListPreference) findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME);
|
||||
networkTimeout = (ListPreference) findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT);
|
||||
maxAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS);
|
||||
maxSongs = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_SONGS);
|
||||
maxArtists = (ListPreference) findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS);
|
||||
defaultArtists = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS);
|
||||
defaultSongs = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS);
|
||||
defaultAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS);
|
||||
chatRefreshInterval = (ListPreference) findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL);
|
||||
directoryCacheTime = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME);
|
||||
mediaButtonsEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS);
|
||||
lockScreenEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS);
|
||||
sendBluetoothAlbumArt = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART);
|
||||
sendBluetoothNotifications = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS);
|
||||
viewRefresh = (ListPreference) findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH);
|
||||
imageLoaderConcurrency = (ListPreference) findPreference(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY);
|
||||
sharingDefaultDescription = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION);
|
||||
sharingDefaultGreeting = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING);
|
||||
sharingDefaultExpiration = (TimeSpanPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION);
|
||||
serversCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_SERVERS_KEY);
|
||||
preloadCount = findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT);
|
||||
bufferLength = findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH);
|
||||
incrementTime = findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME);
|
||||
networkTimeout = findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT);
|
||||
maxAlbums = findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS);
|
||||
maxSongs = findPreference(Constants.PREFERENCES_KEY_MAX_SONGS);
|
||||
maxArtists = findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS);
|
||||
defaultArtists = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS);
|
||||
defaultSongs = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS);
|
||||
defaultAlbums = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS);
|
||||
chatRefreshInterval = findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL);
|
||||
directoryCacheTime = findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME);
|
||||
mediaButtonsEnabled = findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS);
|
||||
lockScreenEnabled = findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS);
|
||||
sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART);
|
||||
sendBluetoothNotifications = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS);
|
||||
viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH);
|
||||
imageLoaderConcurrency = findPreference(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY);
|
||||
sharingDefaultDescription = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION);
|
||||
sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING);
|
||||
sharingDefaultExpiration = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION);
|
||||
resumeOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE);
|
||||
pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE);
|
||||
debugLogToFile = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE);
|
||||
showArtistPicture = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE);
|
||||
debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE);
|
||||
showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE);
|
||||
|
||||
setupServersCategory();
|
||||
sharingDefaultGreeting.setText(Util.getShareGreeting(getActivity()));
|
||||
setupClearSearchPreference();
|
||||
setupGaplessControlSettingsV14();
|
||||
@ -133,9 +148,11 @@ public class SettingsFragment extends PreferenceFragment
|
||||
|
||||
// After API26 foreground services must be used for music playback, and they must have a notification
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PreferenceCategory notificationsCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS);
|
||||
notificationsCategory.removePreference(findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION));
|
||||
notificationsCategory.removePreference(findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION));
|
||||
PreferenceCategory notificationsCategory = findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS);
|
||||
Preference preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION);
|
||||
if (preferenceToRemove != null) notificationsCategory.removePreference(preferenceToRemove);
|
||||
preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION);
|
||||
if (preferenceToRemove != null) notificationsCategory.removePreference(preferenceToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,8 +166,6 @@ public class SettingsFragment extends PreferenceFragment
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
setupServersCategory();
|
||||
SharedPreferences preferences = Util.getPreferences(getActivity());
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
@ -158,7 +173,6 @@ public class SettingsFragment extends PreferenceFragment
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
SharedPreferences prefs = Util.getPreferences(getActivity());
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
@ -179,8 +193,32 @@ public class SettingsFragment extends PreferenceFragment
|
||||
} else if (Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE.equals(key)) {
|
||||
setDebugLogToFile(sharedPreferences.getBoolean(key, false));
|
||||
} else if (Constants.PREFERENCES_KEY_ID3_TAGS.equals(key)) {
|
||||
if (sharedPreferences.getBoolean(key, false)) showArtistPicture.setEnabled(true);
|
||||
else showArtistPicture.setEnabled(false);
|
||||
showArtistPicture.setEnabled(sharedPreferences.getBoolean(key, false));
|
||||
} else if (Constants.PREFERENCES_KEY_THEME.equals(key)) {
|
||||
themeChangedEventDistributor.getValue().RaiseThemeChangedEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference)
|
||||
{
|
||||
DialogFragment dialogFragment = null;
|
||||
if (preference instanceof TimeSpanPreference)
|
||||
{
|
||||
dialogFragment = new TimeSpanPreferenceDialogFragmentCompat();
|
||||
Bundle bundle = new Bundle(1);
|
||||
bundle.putString("key", preference.getKey());
|
||||
dialogFragment.setArguments(bundle);
|
||||
}
|
||||
|
||||
if (dialogFragment != null)
|
||||
{
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
dialogFragment.show(this.getParentFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG");
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,15 +234,15 @@ public class SettingsFragment extends PreferenceFragment
|
||||
@Override
|
||||
public void onPermissionRequestFinished(boolean hasPermission) {
|
||||
if (hasPermission) {
|
||||
FilePickerDialog filePickerDialog = FilePickerDialog.Companion.createFilePickerDialog(getActivity());
|
||||
FilePickerDialog filePickerDialog = FilePickerDialog.Companion.createFilePickerDialog(getContext());
|
||||
filePickerDialog.setDefaultDirectory(FileUtil.getDefaultMusicDirectory(getActivity()).getPath());
|
||||
filePickerDialog.setInitialDirectory(cacheLocation.getSummary().toString());
|
||||
filePickerDialog.setOnFileSelectedListener(new OnFileSelectedListener() {
|
||||
@Override
|
||||
public void onFileSelected(File file, String path) {
|
||||
SharedPreferences.Editor editor = cacheLocation.getEditor();
|
||||
SharedPreferences.Editor editor = cacheLocation.getSharedPreferences().edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, path);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
|
||||
setCacheLocation(path);
|
||||
}
|
||||
@ -234,9 +272,9 @@ public class SettingsFragment extends PreferenceFragment
|
||||
new Consumer<Integer>() {
|
||||
@Override
|
||||
public void accept(Integer choice) {
|
||||
SharedPreferences.Editor editor = resumeOnBluetoothDevice.getEditor();
|
||||
SharedPreferences.Editor editor = resumeOnBluetoothDevice.getSharedPreferences().edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice));
|
||||
}
|
||||
});
|
||||
@ -253,9 +291,9 @@ public class SettingsFragment extends PreferenceFragment
|
||||
new Consumer<Integer>() {
|
||||
@Override
|
||||
public void accept(Integer choice) {
|
||||
SharedPreferences.Editor editor = pauseOnBluetoothDevice.getEditor();
|
||||
SharedPreferences.Editor editor = pauseOnBluetoothDevice.getSharedPreferences().edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE, choice);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice));
|
||||
}
|
||||
});
|
||||
@ -330,7 +368,7 @@ public class SettingsFragment extends PreferenceFragment
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
featureStorage.changeFeatureFlag(Feature.NEW_IMAGE_DOWNLOADER, (Boolean) o);
|
||||
((SubsonicTabActivity) getActivity()).clearImageLoader();
|
||||
imageLoader.getValue().clearImageLoader();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -355,9 +393,9 @@ public class SettingsFragment extends PreferenceFragment
|
||||
private void setupGaplessControlSettingsV14() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
PreferenceCategory playbackControlSettings =
|
||||
(PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_PLAYBACK_CONTROL_SETTINGS);
|
||||
findPreference(Constants.PREFERENCES_KEY_PLAYBACK_CONTROL_SETTINGS);
|
||||
CheckBoxPreference gaplessPlaybackEnabled =
|
||||
(CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK);
|
||||
findPreference(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK);
|
||||
|
||||
if (gaplessPlaybackEnabled != null) {
|
||||
gaplessPlaybackEnabled.setChecked(false);
|
||||
@ -371,25 +409,19 @@ public class SettingsFragment extends PreferenceFragment
|
||||
}
|
||||
|
||||
private void setupServersCategory() {
|
||||
final Preference addServerPreference = new Preference(getActivity());
|
||||
addServerPreference.setPersistent(false);
|
||||
addServerPreference.setTitle(getResources().getString(R.string.settings_server_manage_servers));
|
||||
addServerPreference.setEnabled(true);
|
||||
|
||||
// TODO new server management here
|
||||
serversCategory.removeAll();
|
||||
serversCategory.addPreference(addServerPreference);
|
||||
|
||||
addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final Intent intent = new Intent(getActivity(), ServerSelectorActivity.class);
|
||||
intent.putExtra(SERVER_SELECTOR_MANAGE_MODE, true);
|
||||
startActivityForResult(intent, 0);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(SERVER_SELECTOR_MANAGE_MODE, true);
|
||||
Navigation.findNavController(getView()).navigate(R.id.settingsToServerSelector, bundle);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void update() {
|
||||
@ -433,20 +465,15 @@ public class SettingsFragment extends PreferenceFragment
|
||||
debugLogToFile.setSummary("");
|
||||
}
|
||||
|
||||
if (Util.getShouldUseId3Tags(getActivity())) showArtistPicture.setEnabled(true);
|
||||
else showArtistPicture.setEnabled(false);
|
||||
showArtistPicture.setEnabled(Util.getShouldUseId3Tags(getActivity()));
|
||||
}
|
||||
|
||||
private static void setImageLoaderConcurrency(int concurrency) {
|
||||
SubsonicTabActivity instance = SubsonicTabActivity.getInstance();
|
||||
private void setImageLoaderConcurrency(int concurrency) {
|
||||
ImageLoader imageLoaderInstance = imageLoader.getValue().getImageLoader();
|
||||
|
||||
if (instance != null) {
|
||||
ImageLoader imageLoader = instance.getImageLoader();
|
||||
|
||||
if (imageLoader != null) {
|
||||
imageLoader.stopImageLoader();
|
||||
imageLoader.setConcurrency(concurrency);
|
||||
}
|
||||
if (imageLoaderInstance != null) {
|
||||
imageLoaderInstance.stopImageLoader();
|
||||
imageLoaderInstance.setConcurrency(concurrency);
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,18 +502,14 @@ public class SettingsFragment extends PreferenceFragment
|
||||
}
|
||||
|
||||
private void setBluetoothPreferences(boolean enabled) {
|
||||
if (enabled) {
|
||||
sendBluetoothAlbumArt.setEnabled(true);
|
||||
} else {
|
||||
sendBluetoothAlbumArt.setEnabled(false);
|
||||
}
|
||||
sendBluetoothAlbumArt.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private void setCacheLocation(String path) {
|
||||
File dir = new File(path);
|
||||
|
||||
if (!FileUtil.ensureDirectoryExistsAndIsReadWritable(dir)) {
|
||||
PermissionUtil.handlePermissionFailed(getActivity(), new PermissionUtil.PermissionRequestFinishedCallback() {
|
||||
permissionUtil.getValue().handlePermissionFailed(new PermissionUtil.PermissionRequestFinishedCallback() {
|
||||
@Override
|
||||
public void onPermissionRequestFinished(boolean hasPermission) {
|
||||
String currentPath = settings.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
||||
|
@ -0,0 +1,331 @@
|
||||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
|
||||
import org.moire.ultrasonic.domain.Share;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.service.OfflineException;
|
||||
import org.moire.ultrasonic.subsonic.DownloadHandler;
|
||||
import org.moire.ultrasonic.util.BackgroundTask;
|
||||
import org.moire.ultrasonic.util.CancellationToken;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.LoadingTask;
|
||||
import org.moire.ultrasonic.util.FragmentBackgroundTask;
|
||||
import org.moire.ultrasonic.util.TimeSpan;
|
||||
import org.moire.ultrasonic.util.TimeSpanPicker;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
import org.moire.ultrasonic.view.ShareAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Displays the shares in the media library
|
||||
*/
|
||||
public class SharesFragment extends Fragment {
|
||||
|
||||
private SwipeRefreshLayout refreshSharesListView;
|
||||
private ListView sharesListView;
|
||||
private View emptyTextView;
|
||||
private ShareAdapter shareAdapter;
|
||||
|
||||
private final Lazy<DownloadHandler> downloadHandler = inject(DownloadHandler.class);
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Util.applyTheme(this.getContext());
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.select_share, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
cancellationToken = new CancellationToken();
|
||||
|
||||
refreshSharesListView = view.findViewById(R.id.select_share_refresh);
|
||||
sharesListView = view.findViewById(R.id.select_share_list);
|
||||
|
||||
refreshSharesListView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
|
||||
{
|
||||
@Override
|
||||
public void onRefresh()
|
||||
{
|
||||
load(true);
|
||||
}
|
||||
});
|
||||
|
||||
emptyTextView = view.findViewById(R.id.select_share_empty);
|
||||
sharesListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Share share = (Share) parent.getItemAtPosition(position);
|
||||
|
||||
if (share == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_SHARE_ID, share.getId());
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_SHARE_NAME, share.getName());
|
||||
Navigation.findNavController(view).navigate(R.id.selectAlbumFragment, bundle);
|
||||
}
|
||||
});
|
||||
registerForContextMenu(sharesListView);
|
||||
FragmentTitle.Companion.setTitle(this, R.string.button_bar_shares);
|
||||
|
||||
load(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancellationToken.cancel();
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void load(final boolean refresh)
|
||||
{
|
||||
BackgroundTask<List<Share>> task = new FragmentBackgroundTask<List<Share>>(getActivity(), true, refreshSharesListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected List<Share> doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
return musicService.getShares(refresh, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(List<Share> result)
|
||||
{
|
||||
sharesListView.setAdapter(shareAdapter = new ShareAdapter(getContext(), result));
|
||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NotNull ContextMenu menu, @NotNull View view, ContextMenu.ContextMenuInfo menuInfo)
|
||||
{
|
||||
super.onCreateContextMenu(menu, view, menuInfo);
|
||||
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.select_share_context, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem menuItem)
|
||||
{
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
if (info == null) return false;
|
||||
|
||||
Share share = (Share) sharesListView.getItemAtPosition(info.position);
|
||||
if (share == null || share.getId() == null) return false;
|
||||
|
||||
int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.share_menu_pin) {
|
||||
downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), true, true, false, false, true, false, false);
|
||||
} else if (itemId == R.id.share_menu_unpin) {
|
||||
downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, false, false, true, false, true);
|
||||
} else if (itemId == R.id.share_menu_download) {
|
||||
downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, false, false, true, false, false);
|
||||
} else if (itemId == R.id.share_menu_play_now) {
|
||||
downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, true, false, false, false, false);
|
||||
} else if (itemId == R.id.share_menu_play_shuffled) {
|
||||
downloadHandler.getValue().downloadShare(this, share.getId(), share.getName(), false, false, true, true, false, false, false);
|
||||
} else if (itemId == R.id.share_menu_delete) {
|
||||
deleteShare(share);
|
||||
} else if (itemId == R.id.share_info) {
|
||||
displayShareInfo(share);
|
||||
} else if (itemId == R.id.share_update_info) {
|
||||
updateShareInfo(share);
|
||||
} else {
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void deleteShare(final Share share)
|
||||
{
|
||||
new AlertDialog.Builder(getContext()).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.common_confirm).setMessage(getResources().getString(R.string.delete_playlist, share.getName())).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(getActivity(), refreshSharesListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
musicService.deleteShare(share.getId(), getContext());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
shareAdapter.remove(share);
|
||||
shareAdapter.notifyDataSetChanged();
|
||||
Util.toast(getContext(), getResources().getString(R.string.menu_deleted_share, share.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.menu_deleted_share_error, share.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(getContext(), msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
}).setNegativeButton(R.string.common_cancel, null).show();
|
||||
}
|
||||
|
||||
private void displayShareInfo(final Share share)
|
||||
{
|
||||
final TextView textView = new TextView(getContext());
|
||||
textView.setPadding(5, 5, 5, 5);
|
||||
|
||||
final Spannable message = new SpannableString("Owner: " + share.getUsername() +
|
||||
"\nComments: " + ((share.getDescription() == null) ? "" : share.getDescription()) +
|
||||
"\nURL: " + share.getUrl() +
|
||||
"\nEntry Count: " + share.getEntries().size() +
|
||||
"\nVisit Count: " + share.getVisitCount() +
|
||||
((share.getCreated() == null) ? "" : ("\nCreation Date: " + share.getCreated().replace('T', ' '))) +
|
||||
((share.getLastVisited() == null) ? "" : ("\nLast Visited Date: " + share.getLastVisited().replace('T', ' '))) +
|
||||
((share.getExpires() == null) ? "" : ("\nExpiration Date: " + share.getExpires().replace('T', ' '))));
|
||||
|
||||
Linkify.addLinks(message, Linkify.WEB_URLS);
|
||||
textView.setText(message);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
new AlertDialog.Builder(getContext()).setTitle("Share Details").setCancelable(true).setIcon(android.R.drawable.ic_dialog_info).setView(textView).show();
|
||||
}
|
||||
|
||||
private void updateShareInfo(final Share share)
|
||||
{
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.share_details, null);
|
||||
if (dialogView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final EditText shareDescription = dialogView.findViewById(R.id.share_description);
|
||||
final TimeSpanPicker timeSpanPicker = dialogView.findViewById(R.id.date_picker);
|
||||
|
||||
shareDescription.setText(share.getDescription());
|
||||
|
||||
CheckBox hideDialogCheckBox = dialogView.findViewById(R.id.hide_dialog);
|
||||
CheckBox saveAsDefaultsCheckBox = dialogView.findViewById(R.id.save_as_defaults);
|
||||
CheckBox noExpirationCheckBox = dialogView.findViewById(R.id.timeSpanDisableCheckBox);
|
||||
|
||||
noExpirationCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b)
|
||||
{
|
||||
timeSpanPicker.setEnabled(!b);
|
||||
}
|
||||
});
|
||||
|
||||
noExpirationCheckBox.setChecked(true);
|
||||
|
||||
timeSpanPicker.setTimeSpanDisableText(getResources().getText(R.string.no_expiration));
|
||||
|
||||
hideDialogCheckBox.setVisibility(View.GONE);
|
||||
saveAsDefaultsCheckBox.setVisibility(View.GONE);
|
||||
|
||||
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getContext());
|
||||
|
||||
alertDialog.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alertDialog.setTitle(R.string.playlist_update_info);
|
||||
alertDialog.setView(dialogView);
|
||||
alertDialog.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
new LoadingTask<Void>(getActivity(), refreshSharesListView, cancellationToken)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
long millis = timeSpanPicker.getTimeSpan().getTotalMilliseconds();
|
||||
|
||||
if (millis > 0)
|
||||
{
|
||||
millis = TimeSpan.getCurrentTime().add(millis).getTotalMilliseconds();
|
||||
}
|
||||
|
||||
Editable shareDescriptionText = shareDescription.getText();
|
||||
String description = shareDescriptionText != null ? shareDescriptionText.toString() : null;
|
||||
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(getContext());
|
||||
musicService.updateShare(share.getId(), description, millis, getContext());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result)
|
||||
{
|
||||
load(true);
|
||||
Util.toast(getContext(), getResources().getString(R.string.playlist_updated_info, share.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void error(Throwable error)
|
||||
{
|
||||
String msg;
|
||||
msg = error instanceof OfflineException || error instanceof ApiNotSupportedException ? getErrorMessage(error) : String.format("%s %s", getResources().getString(R.string.playlist_updated_info_error, share.getName()), getErrorMessage(error));
|
||||
|
||||
Util.toast(getContext(), msg, false);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
alertDialog.setNegativeButton(R.string.common_cancel, null);
|
||||
alertDialog.show();
|
||||
}
|
||||
}
|
@ -14,14 +14,16 @@ import android.view.KeyEvent;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.DownloadActivity;
|
||||
import org.moire.ultrasonic.activity.MainActivity;
|
||||
import org.moire.ultrasonic.activity.NavigationActivity;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
|
||||
/**
|
||||
* Widget Provider for the Ultrasonic Widgets
|
||||
*/
|
||||
public class UltrasonicAppWidgetProvider extends AppWidgetProvider
|
||||
{
|
||||
protected int layoutId;
|
||||
@ -41,6 +43,8 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider
|
||||
final Resources res = context.getResources();
|
||||
final RemoteViews views = new RemoteViews(context.getPackageName(), this.layoutId);
|
||||
|
||||
views.setTextViewText(R.id.title, null);
|
||||
views.setTextViewText(R.id.album, null);
|
||||
views.setTextViewText(R.id.artist, res.getText(R.string.widget_initial_text));
|
||||
|
||||
linkButtons(context, views, false);
|
||||
@ -72,7 +76,7 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider
|
||||
{
|
||||
if (hasInstances(context))
|
||||
{
|
||||
performUpdate(context, currentSong, null, playing, setAlbum);
|
||||
performUpdate(context, currentSong, playing, setAlbum);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +99,7 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider
|
||||
/**
|
||||
* Update all active widget instances by pushing changes
|
||||
*/
|
||||
private void performUpdate(Context context, MusicDirectory.Entry currentSong, int[] appWidgetIds, boolean playing, boolean setAlbum)
|
||||
private void performUpdate(Context context, MusicDirectory.Entry currentSong, boolean playing, boolean setAlbum)
|
||||
{
|
||||
final Resources res = context.getResources();
|
||||
final RemoteViews views = new RemoteViews(context.getPackageName(), this.layoutId);
|
||||
@ -176,20 +180,18 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider
|
||||
// Link actions buttons to intents
|
||||
linkButtons(context, views, currentSong != null);
|
||||
|
||||
pushUpdate(context, appWidgetIds, views);
|
||||
pushUpdate(context, null, views);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link up various button actions using {@link PendingIntent}.
|
||||
*
|
||||
* @param playerActive True if player is active in background, which means
|
||||
* widget click will launch {@link DownloadActivity},
|
||||
* otherwise we launch {@link MainActivity}.
|
||||
*/
|
||||
private static void linkButtons(Context context, RemoteViews views, boolean playerActive)
|
||||
{
|
||||
Intent intent = new Intent(context, NavigationActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
if (playerActive)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true);
|
||||
|
||||
Intent intent = new Intent(context, playerActive ? DownloadActivity.class : MainActivity.class);
|
||||
intent.setAction("android.intent.action.MAIN");
|
||||
intent.addCategory("android.intent.category.LAUNCHER");
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 10, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
@ -198,21 +200,18 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider
|
||||
|
||||
// Emulate media button clicks.
|
||||
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
//intent.setPackage(context.getPackageName());
|
||||
intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class));
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
|
||||
pendingIntent = PendingIntent.getBroadcast(context, 11, intent, 0);
|
||||
views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
|
||||
|
||||
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
//intent.setPackage(context.getPackageName());
|
||||
intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class));
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
|
||||
pendingIntent = PendingIntent.getBroadcast(context, 12, intent, 0);
|
||||
views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
|
||||
|
||||
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
//intent.setPackage(context.getPackageName());
|
||||
intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class));
|
||||
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
|
||||
pendingIntent = PendingIntent.getBroadcast(context, 13, intent, 0);
|
||||
|
@ -39,7 +39,6 @@ import org.moire.ultrasonic.domain.UserInfo;
|
||||
import org.moire.ultrasonic.util.CancellableTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.LRUCache;
|
||||
import org.moire.ultrasonic.util.ProgressListener;
|
||||
import org.moire.ultrasonic.util.TimeLimitedCache;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
@ -59,7 +58,7 @@ import static org.koin.java.KoinJavaComponent.inject;
|
||||
*/
|
||||
public class CachedMusicService implements MusicService
|
||||
{
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
||||
private static final int MUSIC_DIR_CACHE_SIZE = 100;
|
||||
|
||||
@ -68,49 +67,49 @@ public class CachedMusicService implements MusicService
|
||||
private final LRUCache<String, TimeLimitedCache<MusicDirectory>> cachedArtist;
|
||||
private final LRUCache<String, TimeLimitedCache<MusicDirectory>> cachedAlbum;
|
||||
private final LRUCache<String, TimeLimitedCache<UserInfo>> cachedUserInfo;
|
||||
private final TimeLimitedCache<Boolean> cachedLicenseValid = new TimeLimitedCache<Boolean>(120, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<Indexes> cachedIndexes = new TimeLimitedCache<Indexes>(60 * 60, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<Indexes> cachedArtists = new TimeLimitedCache<Indexes>(60 * 60, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<Playlist>> cachedPlaylists = new TimeLimitedCache<List<Playlist>>(3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<PodcastsChannel>> cachedPodcastsChannels = new TimeLimitedCache<List<PodcastsChannel>>(3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<MusicFolder>> cachedMusicFolders = new TimeLimitedCache<List<MusicFolder>>(10 * 3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<Boolean> cachedLicenseValid = new TimeLimitedCache<>(120, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<Indexes> cachedIndexes = new TimeLimitedCache<>(60 * 60, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<Indexes> cachedArtists = new TimeLimitedCache<>(60 * 60, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<Playlist>> cachedPlaylists = new TimeLimitedCache<>(3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<PodcastsChannel>> cachedPodcastsChannels = new TimeLimitedCache<>(3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<MusicFolder>> cachedMusicFolders = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS);
|
||||
private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<>(10 * 3600, TimeUnit.SECONDS);
|
||||
|
||||
private String restUrl;
|
||||
|
||||
public CachedMusicService(MusicService musicService)
|
||||
{
|
||||
this.musicService = musicService;
|
||||
cachedMusicDirectories = new LRUCache<String, TimeLimitedCache<MusicDirectory>>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedArtist = new LRUCache<String, TimeLimitedCache<MusicDirectory>>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedAlbum = new LRUCache<String, TimeLimitedCache<MusicDirectory>>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedUserInfo = new LRUCache<String, TimeLimitedCache<UserInfo>>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedMusicDirectories = new LRUCache<>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedArtist = new LRUCache<>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedAlbum = new LRUCache<>(MUSIC_DIR_CACHE_SIZE);
|
||||
cachedUserInfo = new LRUCache<>(MUSIC_DIR_CACHE_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ping(Context context, ProgressListener progressListener) throws Exception
|
||||
public void ping(Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
musicService.ping(context, progressListener);
|
||||
checkSettingsChanged();
|
||||
musicService.ping(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception
|
||||
public boolean isLicenseValid(Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
Boolean result = cachedLicenseValid.get();
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.isLicenseValid(context, progressListener);
|
||||
result = musicService.isLicenseValid(context);
|
||||
cachedLicenseValid.set(result, result ? 30L * 60L : 2L * 60L, TimeUnit.SECONDS);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<MusicFolder> getMusicFolders(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
if (refresh)
|
||||
{
|
||||
cachedMusicFolders.clear();
|
||||
@ -118,16 +117,16 @@ public class CachedMusicService implements MusicService
|
||||
List<MusicFolder> result = cachedMusicFolders.get();
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.getMusicFolders(refresh, context, progressListener);
|
||||
result = musicService.getMusicFolders(refresh, context);
|
||||
cachedMusicFolders.set(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
if (refresh)
|
||||
{
|
||||
cachedIndexes.clear();
|
||||
@ -137,16 +136,16 @@ public class CachedMusicService implements MusicService
|
||||
Indexes result = cachedIndexes.get();
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.getIndexes(musicFolderId, refresh, context, progressListener);
|
||||
result = musicService.getIndexes(musicFolderId, refresh, context);
|
||||
cachedIndexes.set(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Indexes getArtists(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public Indexes getArtists(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
if (refresh)
|
||||
{
|
||||
cachedArtists.clear();
|
||||
@ -154,24 +153,24 @@ public class CachedMusicService implements MusicService
|
||||
Indexes result = cachedArtists.get();
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.getArtists(refresh, context, progressListener);
|
||||
result = musicService.getArtists(refresh, context);
|
||||
cachedArtists.set(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedMusicDirectories.get(id);
|
||||
|
||||
MusicDirectory dir = cache == null ? null : cache.get();
|
||||
|
||||
if (dir == null)
|
||||
{
|
||||
dir = musicService.getMusicDirectory(id, name, refresh, context, progressListener);
|
||||
cache = new TimeLimitedCache<MusicDirectory>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
dir = musicService.getMusicDirectory(id, name, refresh, context);
|
||||
cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
cache.set(dir);
|
||||
cachedMusicDirectories.put(id, cache);
|
||||
}
|
||||
@ -179,15 +178,15 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedArtist.get(id);
|
||||
MusicDirectory dir = cache == null ? null : cache.get();
|
||||
if (dir == null)
|
||||
{
|
||||
dir = musicService.getArtist(id, name, refresh, context, progressListener);
|
||||
cache = new TimeLimitedCache<MusicDirectory>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
dir = musicService.getArtist(id, name, refresh, context);
|
||||
cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
cache.set(dir);
|
||||
cachedArtist.put(id, cache);
|
||||
}
|
||||
@ -195,15 +194,15 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedAlbum.get(id);
|
||||
MusicDirectory dir = cache == null ? null : cache.get();
|
||||
if (dir == null)
|
||||
{
|
||||
dir = musicService.getAlbum(id, name, refresh, context, progressListener);
|
||||
cache = new TimeLimitedCache<MusicDirectory>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
dir = musicService.getAlbum(id, name, refresh, context);
|
||||
cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
cache.set(dir);
|
||||
cachedAlbum.put(id, cache);
|
||||
}
|
||||
@ -211,113 +210,113 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception
|
||||
public SearchResult search(SearchCriteria criteria, Context context) throws Exception
|
||||
{
|
||||
return musicService.search(criteria, context, progressListener);
|
||||
return musicService.search(criteria, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getPlaylist(String id, String name, Context context) throws Exception
|
||||
{
|
||||
return musicService.getPlaylist(id, name, context, progressListener);
|
||||
return musicService.getPlaylist(id, name, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
checkSettingsChanged(context);
|
||||
public List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context) throws Exception {
|
||||
checkSettingsChanged();
|
||||
List<PodcastsChannel> result = refresh ? null : cachedPodcastsChannels.get();
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.getPodcastsChannels(refresh, context, progressListener);
|
||||
result = musicService.getPodcastsChannels(refresh, context);
|
||||
cachedPodcastsChannels.set(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) throws Exception {
|
||||
return musicService.getPodcastEpisodes(podcastChannelId,context,progressListener);
|
||||
public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context) throws Exception {
|
||||
return musicService.getPodcastEpisodes(podcastChannelId,context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
List<Playlist> result = refresh ? null : cachedPlaylists.get();
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.getPlaylists(refresh, context, progressListener);
|
||||
result = musicService.getPlaylists(refresh, context);
|
||||
cachedPlaylists.set(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context) throws Exception
|
||||
{
|
||||
cachedPlaylists.clear();
|
||||
musicService.createPlaylist(id, name, entries, context, progressListener);
|
||||
musicService.createPlaylist(id, name, entries, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception
|
||||
public void deletePlaylist(String id, Context context) throws Exception
|
||||
{
|
||||
musicService.deletePlaylist(id, context, progressListener);
|
||||
musicService.deletePlaylist(id, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception
|
||||
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context) throws Exception
|
||||
{
|
||||
musicService.updatePlaylist(id, name, comment, pub, context, progressListener);
|
||||
musicService.updatePlaylist(id, name, comment, pub, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception
|
||||
public Lyrics getLyrics(String artist, String title, Context context) throws Exception
|
||||
{
|
||||
return musicService.getLyrics(artist, title, context, progressListener);
|
||||
return musicService.getLyrics(artist, title, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception
|
||||
public void scrobble(String id, boolean submission, Context context) throws Exception
|
||||
{
|
||||
musicService.scrobble(id, submission, context, progressListener);
|
||||
musicService.scrobble(id, submission, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception
|
||||
{
|
||||
return musicService.getAlbumList(type, size, offset, context, progressListener);
|
||||
return musicService.getAlbumList(type, size, offset, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList2(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getAlbumList2(String type, int size, int offset, Context context) throws Exception
|
||||
{
|
||||
return musicService.getAlbumList2(type, size, offset, context, progressListener);
|
||||
return musicService.getAlbumList2(type, size, offset, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getRandomSongs(int size, Context context) throws Exception
|
||||
{
|
||||
return musicService.getRandomSongs(size, context, progressListener);
|
||||
return musicService.getRandomSongs(size, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult getStarred(Context context, ProgressListener progressListener) throws Exception
|
||||
public SearchResult getStarred(Context context) throws Exception
|
||||
{
|
||||
return musicService.getStarred(context, progressListener);
|
||||
return musicService.getStarred(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult getStarred2(Context context, ProgressListener progressListener) throws Exception
|
||||
public SearchResult getStarred2(Context context) throws Exception
|
||||
{
|
||||
return musicService.getStarred2(context, progressListener);
|
||||
return musicService.getStarred2(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality) throws Exception
|
||||
{
|
||||
return musicService.getCoverArt(context, entry, size, saveToFile, highQuality, progressListener);
|
||||
return musicService.getCoverArt(context, entry, size, saveToFile, highQuality);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -333,42 +332,42 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context) throws Exception
|
||||
{
|
||||
return musicService.updateJukeboxPlaylist(ids, context, progressListener);
|
||||
return musicService.updateJukeboxPlaylist(ids, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context) throws Exception
|
||||
{
|
||||
return musicService.skipJukebox(index, offsetSeconds, context, progressListener);
|
||||
return musicService.skipJukebox(index, offsetSeconds, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus stopJukebox(Context context) throws Exception
|
||||
{
|
||||
return musicService.stopJukebox(context, progressListener);
|
||||
return musicService.stopJukebox(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus startJukebox(Context context) throws Exception
|
||||
{
|
||||
return musicService.startJukebox(context, progressListener);
|
||||
return musicService.startJukebox(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus getJukeboxStatus(Context context) throws Exception
|
||||
{
|
||||
return musicService.getJukeboxStatus(context, progressListener);
|
||||
return musicService.getJukeboxStatus(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus setJukeboxGain(float gain, Context context) throws Exception
|
||||
{
|
||||
return musicService.setJukeboxGain(gain, context, progressListener);
|
||||
return musicService.setJukeboxGain(gain, context);
|
||||
}
|
||||
|
||||
private void checkSettingsChanged(Context context)
|
||||
private void checkSettingsChanged()
|
||||
{
|
||||
String newUrl = activeServerProvider.getValue().getRestUrl(null);
|
||||
if (!Util.equals(newUrl, restUrl))
|
||||
@ -387,27 +386,27 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
||||
public void star(String id, String albumId, String artistId, Context context) throws Exception
|
||||
{
|
||||
musicService.star(id, albumId, artistId, context, progressListener);
|
||||
musicService.star(id, albumId, artistId, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
||||
public void unstar(String id, String albumId, String artistId, Context context) throws Exception
|
||||
{
|
||||
musicService.unstar(id, albumId, artistId, context, progressListener);
|
||||
musicService.unstar(id, albumId, artistId, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRating(String id, int rating, Context context, ProgressListener progressListener) throws Exception
|
||||
public void setRating(String id, int rating, Context context) throws Exception
|
||||
{
|
||||
musicService.setRating(id, rating, context, progressListener);
|
||||
musicService.setRating(id, rating, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Genre> getGenres(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
if (refresh)
|
||||
{
|
||||
cachedGenres.clear();
|
||||
@ -416,7 +415,7 @@ public class CachedMusicService implements MusicService
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = musicService.getGenres(refresh, context, progressListener);
|
||||
result = musicService.getGenres(refresh, context);
|
||||
cachedGenres.set(result);
|
||||
}
|
||||
|
||||
@ -433,59 +432,59 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context) throws Exception
|
||||
{
|
||||
return musicService.getSongsByGenre(genre, count, offset, context, progressListener);
|
||||
return musicService.getSongsByGenre(genre, count, offset, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Share> getShares(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Share> getShares(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
return musicService.getShares(refresh, context, progressListener);
|
||||
return musicService.getShares(refresh, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<ChatMessage> getChatMessages(Long since, Context context) throws Exception
|
||||
{
|
||||
return musicService.getChatMessages(since, context, progressListener);
|
||||
return musicService.getChatMessages(since, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception
|
||||
public void addChatMessage(String message, Context context) throws Exception
|
||||
{
|
||||
musicService.addChatMessage(message, context, progressListener);
|
||||
musicService.addChatMessage(message, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Bookmark> getBookmarks(Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Bookmark> getBookmarks(Context context) throws Exception
|
||||
{
|
||||
return musicService.getBookmarks(context, progressListener);
|
||||
return musicService.getBookmarks(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBookmark(String id, Context context, ProgressListener progressListener) throws Exception
|
||||
public void deleteBookmark(String id, Context context) throws Exception
|
||||
{
|
||||
musicService.deleteBookmark(id, context, progressListener);
|
||||
musicService.deleteBookmark(id, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBookmark(String id, int position, Context context, ProgressListener progressListener) throws Exception
|
||||
public void createBookmark(String id, int position, Context context) throws Exception
|
||||
{
|
||||
musicService.createBookmark(id, position, context, progressListener);
|
||||
musicService.createBookmark(id, position, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getVideos(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getVideos(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedMusicDirectories.get(Constants.INTENT_EXTRA_NAME_VIDEOS);
|
||||
|
||||
MusicDirectory dir = cache == null ? null : cache.get();
|
||||
|
||||
if (dir == null)
|
||||
{
|
||||
dir = musicService.getVideos(refresh, context, progressListener);
|
||||
cache = new TimeLimitedCache<MusicDirectory>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
dir = musicService.getVideos(refresh, context);
|
||||
cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
cache.set(dir);
|
||||
cachedMusicDirectories.put(Constants.INTENT_EXTRA_NAME_VIDEOS, cache);
|
||||
}
|
||||
@ -494,9 +493,9 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserInfo getUser(String username, Context context, ProgressListener progressListener) throws Exception
|
||||
public UserInfo getUser(String username, Context context) throws Exception
|
||||
{
|
||||
checkSettingsChanged(context);
|
||||
checkSettingsChanged();
|
||||
|
||||
TimeLimitedCache<UserInfo> cache = cachedUserInfo.get(username);
|
||||
|
||||
@ -504,8 +503,8 @@ public class CachedMusicService implements MusicService
|
||||
|
||||
if (userInfo == null)
|
||||
{
|
||||
userInfo = musicService.getUser(username, context, progressListener);
|
||||
cache = new TimeLimitedCache<UserInfo>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
userInfo = musicService.getUser(username, context);
|
||||
cache = new TimeLimitedCache<>(Util.getDirectoryCacheTime(context), TimeUnit.SECONDS);
|
||||
cache.set(userInfo);
|
||||
cachedUserInfo.put(username, cache);
|
||||
}
|
||||
@ -514,27 +513,26 @@ public class CachedMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Share> createShare(List<String> ids, String description, Long expires, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Share> createShare(List<String> ids, String description, Long expires, Context context) throws Exception
|
||||
{
|
||||
return musicService.createShare(ids, description, expires, context, progressListener);
|
||||
return musicService.createShare(ids, description, expires, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteShare(String id, Context context, ProgressListener progressListener) throws Exception
|
||||
public void deleteShare(String id, Context context) throws Exception
|
||||
{
|
||||
musicService.deleteShare(id, context, progressListener);
|
||||
musicService.deleteShare(id, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception
|
||||
public void updateShare(String id, String description, Long expires, Context context) throws Exception
|
||||
{
|
||||
musicService.updateShare(id, description, expires, context, progressListener);
|
||||
musicService.updateShare(id, description, expires, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception
|
||||
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality) throws Exception
|
||||
{
|
||||
return musicService.getAvatar(context, username, size, saveToFile, highQuality, progressListener);
|
||||
return musicService.getAvatar(context, username, size, saveToFile, highQuality);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ public class DownloadFile
|
||||
private volatile boolean saveWhenDone;
|
||||
private volatile boolean completeWhenDone;
|
||||
|
||||
private Lazy<Downloader> downloader = inject(Downloader.class);
|
||||
private final Lazy<Downloader> downloader = inject(Downloader.class);
|
||||
|
||||
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save)
|
||||
{
|
||||
@ -196,7 +196,9 @@ public class DownloadFile
|
||||
{
|
||||
if (saveFile.exists())
|
||||
{
|
||||
saveFile.renameTo(completeFile);
|
||||
if (!saveFile.renameTo(completeFile)){
|
||||
Timber.w("Renaming file failed. Original file: %s; Rename to: %s", saveFile.getName(), completeFile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,13 +458,13 @@ public class DownloadFile
|
||||
return String.format("DownloadTask (%s)", song);
|
||||
}
|
||||
|
||||
private void downloadAndSaveCoverArt(MusicService musicService) throws Exception
|
||||
private void downloadAndSaveCoverArt(MusicService musicService)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TextUtils.isEmpty(song.getCoverArt())) {
|
||||
int size = Util.getMinDisplayMetric(context);
|
||||
musicService.getCoverArt(context, song, size, true, true, null);
|
||||
musicService.getCoverArt(context, song, size, true, true);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
|
@ -67,13 +67,13 @@ public class JukeboxMediaPlayer
|
||||
private JukeboxStatus jukeboxStatus;
|
||||
private float gain = 0.5f;
|
||||
private VolumeToast volumeToast;
|
||||
private AtomicBoolean running = new AtomicBoolean();
|
||||
private final AtomicBoolean running = new AtomicBoolean();
|
||||
private Thread serviceThread;
|
||||
private boolean enabled = false;
|
||||
private Context context;
|
||||
private final Context context;
|
||||
|
||||
// TODO: These create circular references, try to refactor
|
||||
private Lazy<MediaPlayerControllerImpl> mediaPlayerControllerLazy = inject(MediaPlayerControllerImpl.class);
|
||||
private final Lazy<MediaPlayerControllerImpl> mediaPlayerControllerLazy = inject(MediaPlayerControllerImpl.class);
|
||||
private final Downloader downloader;
|
||||
|
||||
// TODO: Report warning if queue fills up.
|
||||
@ -397,7 +397,7 @@ public class JukeboxMediaPlayer
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().getJukeboxStatus(context, null);
|
||||
return getMusicService().getJukeboxStatus(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,7 +413,7 @@ public class JukeboxMediaPlayer
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().updateJukeboxPlaylist(ids, context, null);
|
||||
return getMusicService().updateJukeboxPlaylist(ids, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -431,7 +431,7 @@ public class JukeboxMediaPlayer
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().skipJukebox(index, offsetSeconds, context, null);
|
||||
return getMusicService().skipJukebox(index, offsetSeconds, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -440,7 +440,7 @@ public class JukeboxMediaPlayer
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().stopJukebox(context, null);
|
||||
return getMusicService().stopJukebox(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +449,7 @@ public class JukeboxMediaPlayer
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().startJukebox(context, null);
|
||||
return getMusicService().startJukebox(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,7 +466,7 @@ public class JukeboxMediaPlayer
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().setJukeboxGain(gain, context, null);
|
||||
return getMusicService().setJukeboxGain(gain, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,12 @@ import timber.log.Timber;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.moire.ultrasonic.activity.DownloadActivity;
|
||||
import org.moire.ultrasonic.audiofx.EqualizerController;
|
||||
import org.moire.ultrasonic.audiofx.VisualizerController;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.fragment.PlayerFragment;
|
||||
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
|
||||
import org.moire.ultrasonic.util.CancellableTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
@ -598,7 +598,7 @@ public class LocalMediaPlayer
|
||||
@Override
|
||||
public void onBufferingUpdate(MediaPlayer mp, int percent)
|
||||
{
|
||||
SeekBar progressBar = DownloadActivity.getProgressBar();
|
||||
SeekBar progressBar = PlayerFragment.getProgressBar();
|
||||
MusicDirectory.Entry song = downloadFile.getSong();
|
||||
|
||||
if (percent == 100)
|
||||
@ -627,12 +627,12 @@ public class LocalMediaPlayer
|
||||
|
||||
setPlayerState(PREPARED);
|
||||
|
||||
SeekBar progressBar = DownloadActivity.getProgressBar();
|
||||
SeekBar progressBar = PlayerFragment.getProgressBar();
|
||||
|
||||
if (progressBar != null && downloadFile.isWorkDone())
|
||||
{
|
||||
// Populate seek bar secondary progress if we have a complete file for consistency
|
||||
DownloadActivity.getProgressBar().setSecondaryProgress(100 * progressBar.getMax());
|
||||
PlayerFragment.getProgressBar().setSecondaryProgress(100 * progressBar.getMax());
|
||||
}
|
||||
|
||||
synchronized (LocalMediaPlayer.this)
|
||||
|
@ -23,8 +23,6 @@ import android.content.Intent;
|
||||
import timber.log.Timber;
|
||||
|
||||
import org.koin.java.KoinJavaComponent;
|
||||
import org.moire.ultrasonic.audiofx.EqualizerController;
|
||||
import org.moire.ultrasonic.audiofx.VisualizerController;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
@ -60,9 +58,10 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
|
||||
private boolean showVisualization;
|
||||
private boolean autoPlayStart;
|
||||
|
||||
private Context context;
|
||||
private Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
private final Context context;
|
||||
private final Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
||||
private final DownloadQueueSerializer downloadQueueSerializer;
|
||||
private final ExternalStorageMonitor externalStorageMonitor;
|
||||
private final Downloader downloader;
|
||||
@ -522,7 +521,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
|
||||
try
|
||||
{
|
||||
String username = activeServerProvider.getValue().getActiveServer().getUserName();
|
||||
UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context, null);
|
||||
UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context);
|
||||
return user.getJukeboxRole();
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -595,7 +594,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController
|
||||
{
|
||||
try
|
||||
{
|
||||
MusicServiceFactory.getMusicService(context).setRating(song.getId(), rating, context, null);
|
||||
MusicServiceFactory.getMusicService(context).setRating(song.getId(), rating, context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -20,8 +20,7 @@ import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import org.koin.java.KoinJavaComponent;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.DownloadActivity;
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import org.moire.ultrasonic.activity.NavigationActivity;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.domain.RepeatMode;
|
||||
@ -31,7 +30,9 @@ import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1;
|
||||
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2;
|
||||
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3;
|
||||
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.NowPlayingEventDistributor;
|
||||
import org.moire.ultrasonic.util.ShufflePlayBuffer;
|
||||
import org.moire.ultrasonic.util.SimpleServiceBinder;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
@ -64,10 +65,11 @@ public class MediaPlayerService extends Service
|
||||
private final Scrobbler scrobbler = new Scrobbler();
|
||||
|
||||
public Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
private Lazy<DownloadQueueSerializer> downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class);
|
||||
private Lazy<ShufflePlayBuffer> shufflePlayBufferLazy = inject(ShufflePlayBuffer.class);
|
||||
private Lazy<Downloader> downloaderLazy = inject(Downloader.class);
|
||||
private Lazy<LocalMediaPlayer> localMediaPlayerLazy = inject(LocalMediaPlayer.class);
|
||||
private final Lazy<DownloadQueueSerializer> downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class);
|
||||
private final Lazy<ShufflePlayBuffer> shufflePlayBufferLazy = inject(ShufflePlayBuffer.class);
|
||||
private final Lazy<Downloader> downloaderLazy = inject(Downloader.class);
|
||||
private final Lazy<LocalMediaPlayer> localMediaPlayerLazy = inject(LocalMediaPlayer.class);
|
||||
private final Lazy<NowPlayingEventDistributor> nowPlayingEventDistributor = inject(NowPlayingEventDistributor.class);
|
||||
private LocalMediaPlayer localMediaPlayer;
|
||||
private Downloader downloader;
|
||||
private ShufflePlayBuffer shufflePlayBuffer;
|
||||
@ -280,21 +282,14 @@ public class MediaPlayerService extends Service
|
||||
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
||||
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
||||
|
||||
SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
|
||||
|
||||
if (currentPlaying != null)
|
||||
{
|
||||
updateNotification(localMediaPlayer.playerState, currentPlaying);
|
||||
if (tabInstance != null) {
|
||||
tabInstance.showNowPlaying();
|
||||
}
|
||||
nowPlayingEventDistributor.getValue().raiseShowNowPlayingEvent();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tabInstance != null)
|
||||
{
|
||||
tabInstance.hideNowPlaying();
|
||||
}
|
||||
nowPlayingEventDistributor.getValue().raiseHideNowPlayingEvent();
|
||||
stopForeground(true);
|
||||
localMediaPlayer.clearRemoteControl();
|
||||
isInForeground = false;
|
||||
@ -499,7 +494,6 @@ public class MediaPlayerService extends Service
|
||||
UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true);
|
||||
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
||||
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
|
||||
SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
|
||||
|
||||
if (show)
|
||||
{
|
||||
@ -507,18 +501,12 @@ public class MediaPlayerService extends Service
|
||||
if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)
|
||||
{
|
||||
updateNotification(playerState, currentPlaying);
|
||||
if (tabInstance != null)
|
||||
{
|
||||
tabInstance.showNowPlaying();
|
||||
}
|
||||
nowPlayingEventDistributor.getValue().raiseShowNowPlayingEvent();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tabInstance != null)
|
||||
{
|
||||
tabInstance.hideNowPlaying();
|
||||
}
|
||||
nowPlayingEventDistributor.getValue().raiseHideNowPlayingEvent();
|
||||
stopForeground(true);
|
||||
localMediaPlayer.clearRemoteControl();
|
||||
isInForeground = false;
|
||||
@ -553,7 +541,7 @@ public class MediaPlayerService extends Service
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(MediaPlayerService.this);
|
||||
try
|
||||
{
|
||||
musicService.deleteBookmark(song.getId(), MediaPlayerService.this, null);
|
||||
musicService.deleteBookmark(song.getId(), MediaPlayerService.this);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -650,8 +638,10 @@ public class MediaPlayerService extends Service
|
||||
|
||||
notificationBuilder.setContent(contentView);
|
||||
|
||||
Intent notificationIntent = new Intent(this, DownloadActivity.class);
|
||||
notificationBuilder.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0));
|
||||
Intent notificationIntent = new Intent(this, NavigationActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true);
|
||||
notificationBuilder.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
if (playerState == PlayerState.PAUSED || playerState == PlayerState.IDLE) {
|
||||
contentView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
|
||||
|
@ -36,7 +36,6 @@ import org.moire.ultrasonic.domain.SearchResult;
|
||||
import org.moire.ultrasonic.domain.Share;
|
||||
import org.moire.ultrasonic.domain.UserInfo;
|
||||
import org.moire.ultrasonic.util.CancellableTask;
|
||||
import org.moire.ultrasonic.util.ProgressListener;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
@ -49,61 +48,61 @@ import kotlin.Pair;
|
||||
public interface MusicService
|
||||
{
|
||||
|
||||
void ping(Context context, ProgressListener progressListener) throws Exception;
|
||||
void ping(Context context) throws Exception;
|
||||
|
||||
boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception;
|
||||
boolean isLicenseValid(Context context) throws Exception;
|
||||
|
||||
List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<Genre> getGenres(boolean refresh, Context context) throws Exception;
|
||||
|
||||
void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception;
|
||||
void star(String id, String albumId, String artistId, Context context) throws Exception;
|
||||
|
||||
void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception;
|
||||
void unstar(String id, String albumId, String artistId, Context context) throws Exception;
|
||||
|
||||
void setRating(String id, int rating, Context context, ProgressListener progressListener) throws Exception;
|
||||
void setRating(String id, int rating, Context context) throws Exception;
|
||||
|
||||
List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<MusicFolder> getMusicFolders(boolean refresh, Context context) throws Exception;
|
||||
|
||||
Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
Indexes getIndexes(String musicFolderId, boolean refresh, Context context) throws Exception;
|
||||
|
||||
Indexes getArtists(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
Indexes getArtists(boolean refresh, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getArtist(String id, String name, boolean refresh, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getAlbum(String id, String name, boolean refresh, Context context) throws Exception;
|
||||
|
||||
SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener) throws Exception;
|
||||
SearchResult search(SearchCriteria criteria, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getPlaylist(String id, String name, Context context) throws Exception;
|
||||
|
||||
List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context) throws Exception;
|
||||
|
||||
List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<Playlist> getPlaylists(boolean refresh, Context context) throws Exception;
|
||||
|
||||
void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception;
|
||||
void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context) throws Exception;
|
||||
|
||||
void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||
void deletePlaylist(String id, Context context) throws Exception;
|
||||
|
||||
void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception;
|
||||
void updatePlaylist(String id, String name, String comment, boolean pub, Context context) throws Exception;
|
||||
|
||||
Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception;
|
||||
Lyrics getLyrics(String artist, String title, Context context) throws Exception;
|
||||
|
||||
void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception;
|
||||
void scrobble(String id, boolean submission, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getAlbumList2(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getAlbumList2(String type, int size, int offset, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getRandomSongs(int size, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context) throws Exception;
|
||||
|
||||
SearchResult getStarred(Context context, ProgressListener progressListener) throws Exception;
|
||||
SearchResult getStarred(Context context) throws Exception;
|
||||
|
||||
SearchResult getStarred2(Context context, ProgressListener progressListener) throws Exception;
|
||||
SearchResult getStarred2(Context context) throws Exception;
|
||||
|
||||
Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception;
|
||||
Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality) throws Exception;
|
||||
|
||||
/**
|
||||
* Return response {@link InputStream} and a {@link Boolean} that indicates if this response is
|
||||
@ -111,43 +110,44 @@ public interface MusicService
|
||||
*/
|
||||
Pair<InputStream, Boolean> getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception;
|
||||
|
||||
@Deprecated String getVideoUrl(Context context, String id, boolean useFlash) throws Exception;
|
||||
// TODO: Refactor and remove this call (see RestMusicService implementation)
|
||||
String getVideoUrl(Context context, String id, boolean useFlash) throws Exception;
|
||||
|
||||
JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception;
|
||||
JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context) throws Exception;
|
||||
|
||||
JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception;
|
||||
JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context) throws Exception;
|
||||
|
||||
JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception;
|
||||
JukeboxStatus stopJukebox(Context context) throws Exception;
|
||||
|
||||
JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception;
|
||||
JukeboxStatus startJukebox(Context context) throws Exception;
|
||||
|
||||
JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception;
|
||||
JukeboxStatus getJukeboxStatus(Context context) throws Exception;
|
||||
|
||||
JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception;
|
||||
JukeboxStatus setJukeboxGain(float gain, Context context) throws Exception;
|
||||
|
||||
List<Share> getShares(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<Share> getShares(boolean refresh, Context context) throws Exception;
|
||||
|
||||
List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<ChatMessage> getChatMessages(Long since, Context context) throws Exception;
|
||||
|
||||
void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception;
|
||||
void addChatMessage(String message, Context context) throws Exception;
|
||||
|
||||
List<Bookmark> getBookmarks(Context context, ProgressListener progressListener) throws Exception;
|
||||
List<Bookmark> getBookmarks(Context context) throws Exception;
|
||||
|
||||
void deleteBookmark(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||
void deleteBookmark(String id, Context context) throws Exception;
|
||||
|
||||
void createBookmark(String id, int position, Context context, ProgressListener progressListener) throws Exception;
|
||||
void createBookmark(String id, int position, Context context) throws Exception;
|
||||
|
||||
MusicDirectory getVideos(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getVideos(boolean refresh, Context context) throws Exception;
|
||||
|
||||
UserInfo getUser(String username, Context context, ProgressListener progressListener) throws Exception;
|
||||
UserInfo getUser(String username, Context context) throws Exception;
|
||||
|
||||
List<Share> createShare(List<String> ids, String description, Long expires, Context context, ProgressListener progressListener) throws Exception;
|
||||
List<Share> createShare(List<String> ids, String description, Long expires, Context context) throws Exception;
|
||||
|
||||
void deleteShare(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||
void deleteShare(String id, Context context) throws Exception;
|
||||
|
||||
void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception;
|
||||
void updateShare(String id, String description, Long expires, Context context) throws Exception;
|
||||
|
||||
Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception;
|
||||
Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality) throws Exception;
|
||||
|
||||
MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) throws Exception;
|
||||
MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context) throws Exception;
|
||||
}
|
@ -44,7 +44,6 @@ import org.moire.ultrasonic.domain.UserInfo;
|
||||
import org.moire.ultrasonic.util.CancellableTask;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.ProgressListener;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@ -76,12 +75,12 @@ import static org.koin.java.KoinJavaComponent.inject;
|
||||
public class OfflineMusicService implements MusicService
|
||||
{
|
||||
private static final Pattern COMPILE = Pattern.compile(" ");
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
|
||||
@Override
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context)
|
||||
{
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
List<Artist> artists = new ArrayList<>();
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
for (File file : FileUtil.listFiles(root))
|
||||
{
|
||||
@ -144,13 +143,13 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener)
|
||||
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context)
|
||||
{
|
||||
File dir = new File(id);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
result.setName(dir.getName());
|
||||
|
||||
Collection<String> names = new HashSet<String>();
|
||||
Collection<String> names = new HashSet<>();
|
||||
|
||||
for (File file : FileUtil.listMediaFiles(dir))
|
||||
{
|
||||
@ -335,7 +334,7 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener)
|
||||
public Bitmap getAvatar(Context context, String username, int size, boolean saveToFile, boolean highQuality)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -349,7 +348,7 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener)
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -363,11 +362,11 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(SearchCriteria criteria, Context context, ProgressListener progressListener)
|
||||
public SearchResult search(SearchCriteria criteria, Context context)
|
||||
{
|
||||
List<Artist> artists = new ArrayList<Artist>();
|
||||
List<MusicDirectory.Entry> albums = new ArrayList<MusicDirectory.Entry>();
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
|
||||
List<Artist> artists = new ArrayList<>();
|
||||
List<MusicDirectory.Entry> albums = new ArrayList<>();
|
||||
List<MusicDirectory.Entry> songs = new ArrayList<>();
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
int closeness;
|
||||
|
||||
@ -507,9 +506,9 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener)
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context)
|
||||
{
|
||||
List<Playlist> playlists = new ArrayList<Playlist>();
|
||||
List<Playlist> playlists = new ArrayList<>();
|
||||
File root = FileUtil.getPlaylistDirectory(context);
|
||||
String lastServer = null;
|
||||
boolean removeServer = true;
|
||||
@ -544,11 +543,13 @@ public class OfflineMusicService implements MusicService
|
||||
// Delete legacy playlist files
|
||||
try
|
||||
{
|
||||
folder.delete();
|
||||
if (!folder.delete()) {
|
||||
Timber.w("Failed to delete old playlist file: %s", folder.getName());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Timber.w("Failed to delete old playlist file: %s", folder.getName());
|
||||
Timber.w(e, "Failed to delete old playlist file: %s", folder.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -564,7 +565,7 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getPlaylist(String id, String name, Context context) throws Exception
|
||||
{
|
||||
Reader reader = null;
|
||||
BufferedReader buffer = null;
|
||||
@ -606,7 +607,7 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context) throws Exception
|
||||
{
|
||||
File playlistFile = FileUtil.getPlaylistFile(context, activeServerProvider.getValue().getActiveServer().getName(), name);
|
||||
FileWriter fw = new FileWriter(playlistFile);
|
||||
@ -639,10 +640,10 @@ public class OfflineMusicService implements MusicService
|
||||
|
||||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener)
|
||||
public MusicDirectory getRandomSongs(int size, Context context)
|
||||
{
|
||||
File root = FileUtil.getMusicDirectory(context);
|
||||
List<File> children = new LinkedList<File>();
|
||||
List<File> children = new LinkedList<>();
|
||||
listFilesRecursively(root, children);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
|
||||
@ -677,138 +678,138 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception
|
||||
public void deletePlaylist(String id, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception
|
||||
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Updating playlist not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception
|
||||
public Lyrics getLyrics(String artist, String title, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Lyrics not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception
|
||||
public void scrobble(String id, boolean submission, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Scrobbling not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Album lists not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus skipJukebox(int index, int offsetSeconds, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus stopJukebox(Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus stopJukebox(Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus startJukebox(Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus startJukebox(Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus getJukeboxStatus(Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus getJukeboxStatus(Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception
|
||||
public JukeboxStatus setJukeboxGain(float gain, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Jukebox not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult getStarred(Context context, ProgressListener progressListener) throws Exception
|
||||
public SearchResult getStarred(Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Starred not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception
|
||||
public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Getting Songs By Genre not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Genre> getGenres(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Genre> getGenres(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Getting Genres not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserInfo getUser(String username, Context context, ProgressListener progressListener) throws Exception
|
||||
public UserInfo getUser(String username, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Getting user info not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Share> createShare(List<String> ids, String description, Long expires, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Share> createShare(List<String> ids, String description, Long expires, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Creating shares not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Share> getShares(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<Share> getShares(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Getting shares not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteShare(String id, Context context, ProgressListener progressListener) throws Exception
|
||||
public void deleteShare(String id, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Deleting shares not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateShare(String id, String description, Long expires, Context context, ProgressListener progressListener) throws Exception
|
||||
public void updateShare(String id, String description, Long expires, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Updating shares not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void star(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
||||
public void star(String id, String albumId, String artistId, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Star not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unstar(String id, String albumId, String artistId, Context context, ProgressListener progressListener) throws Exception
|
||||
public void unstar(String id, String albumId, String artistId, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("UnStar not available in offline mode");
|
||||
}
|
||||
@Override
|
||||
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception
|
||||
public List<MusicFolder> getMusicFolders(boolean refresh, Context context) throws Exception
|
||||
{
|
||||
throw new OfflineException("Music folders not available in offline mode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbumList2(String type, int size, int offset, Context context, ProgressListener progressListener) {
|
||||
public MusicDirectory getAlbumList2(String type, int size, int offset, Context context) {
|
||||
Timber.w("OfflineMusicService.getAlbumList2 was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
@ -820,73 +821,73 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) {
|
||||
public List<ChatMessage> getChatMessages(Long since, Context context) {
|
||||
Timber.w("OfflineMusicService.getChatMessages was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addChatMessage(String message, Context context, ProgressListener progressListener) {
|
||||
public void addChatMessage(String message, Context context) {
|
||||
Timber.w("OfflineMusicService.addChatMessage was called but it isn't available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Bookmark> getBookmarks(Context context, ProgressListener progressListener) {
|
||||
public List<Bookmark> getBookmarks(Context context) {
|
||||
Timber.w("OfflineMusicService.getBookmarks was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBookmark(String id, Context context, ProgressListener progressListener) {
|
||||
public void deleteBookmark(String id, Context context) {
|
||||
Timber.w("OfflineMusicService.deleteBookmark was called but it isn't available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBookmark(String id, int position, Context context, ProgressListener progressListener) {
|
||||
public void createBookmark(String id, int position, Context context) {
|
||||
Timber.w("OfflineMusicService.createBookmark was called but it isn't available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getVideos(boolean refresh, Context context, ProgressListener progressListener) {
|
||||
public MusicDirectory getVideos(boolean refresh, Context context) {
|
||||
Timber.w("OfflineMusicService.getVideos was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult getStarred2(Context context, ProgressListener progressListener) {
|
||||
public SearchResult getStarred2(Context context) {
|
||||
Timber.w("OfflineMusicService.getStarred2 was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ping(Context context, ProgressListener progressListener) {
|
||||
public void ping(Context context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) {
|
||||
public boolean isLicenseValid(Context context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Indexes getArtists(boolean refresh, Context context, ProgressListener progressListener) {
|
||||
public Indexes getArtists(boolean refresh, Context context) {
|
||||
Timber.w("OfflineMusicService.getArtists was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context, ProgressListener progressListener) {
|
||||
public MusicDirectory getArtist(String id, String name, boolean refresh, Context context) {
|
||||
Timber.w("OfflineMusicService.getArtist was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context, ProgressListener progressListener) {
|
||||
public MusicDirectory getAlbum(String id, String name, boolean refresh, Context context) {
|
||||
Timber.w("OfflineMusicService.getAlbum was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context, ProgressListener progressListener) {
|
||||
public MusicDirectory getPodcastEpisodes(String podcastChannelId, Context context) {
|
||||
Timber.w("OfflineMusicService.getPodcastEpisodes was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
@ -898,12 +899,12 @@ public class OfflineMusicService implements MusicService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRating(String id, int rating, Context context, ProgressListener progressListener) {
|
||||
public void setRating(String id, int rating, Context context) {
|
||||
Timber.w("OfflineMusicService.setRating was called but it isn't available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context, ProgressListener progressListener) {
|
||||
public List<PodcastsChannel> getPodcastsChannels(boolean refresh, Context context) {
|
||||
Timber.w("OfflineMusicService.getPodcastsChannels was called but it isn't available");
|
||||
return null;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||
import timber.log.Timber;
|
||||
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* Scrobbles played songs to Last.fm.
|
||||
@ -19,32 +18,18 @@ public class Scrobbler
|
||||
|
||||
public void scrobble(final Context context, final DownloadFile song, final boolean submission)
|
||||
{
|
||||
if (song == null || !ActiveServerProvider.Companion.isScrobblingEnabled(context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (song == null || !ActiveServerProvider.Companion.isScrobblingEnabled(context)) return;
|
||||
|
||||
final String id = song.getSong().getId();
|
||||
if (id == null) return;
|
||||
|
||||
// Avoid duplicate registrations.
|
||||
if (submission && id.equals(lastSubmission))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (submission && id.equals(lastSubmission)) return;
|
||||
|
||||
if (!submission && id.equals(lastNowPlaying))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!submission && id.equals(lastNowPlaying)) return;
|
||||
|
||||
if (submission)
|
||||
{
|
||||
lastSubmission = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastNowPlaying = id;
|
||||
}
|
||||
if (submission) lastSubmission = id;
|
||||
else lastNowPlaying = id;
|
||||
|
||||
new Thread(String.format("Scrobble %s", song))
|
||||
{
|
||||
@ -54,7 +39,7 @@ public class Scrobbler
|
||||
MusicService service = MusicServiceFactory.getMusicService(context);
|
||||
try
|
||||
{
|
||||
service.scrobble(id, submission, context, null);
|
||||
service.scrobble(id, submission, context);
|
||||
Timber.i("Scrobbled '%s' for %s", submission ? "submission" : "now playing", song);
|
||||
}
|
||||
catch (Exception x)
|
||||
|
@ -52,13 +52,12 @@ public final class Constants
|
||||
public static final String INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET = "subsonic.albumlistoffset";
|
||||
public static final String INTENT_EXTRA_NAME_SHUFFLE = "subsonic.shuffle";
|
||||
public static final String INTENT_EXTRA_NAME_REFRESH = "subsonic.refresh";
|
||||
public static final String INTENT_EXTRA_REQUEST_SEARCH = "subsonic.requestsearch";
|
||||
public static final String INTENT_EXTRA_NAME_EXIT = "subsonic.exit";
|
||||
public static final String INTENT_EXTRA_NAME_STARRED = "subsonic.starred";
|
||||
public static final String INTENT_EXTRA_NAME_RANDOM = "subsonic.random";
|
||||
public static final String INTENT_EXTRA_NAME_GENRE_NAME = "subsonic.genre";
|
||||
public static final String INTENT_EXTRA_NAME_IS_ALBUM = "subsonic.isalbum";
|
||||
public static final String INTENT_EXTRA_NAME_VIDEOS = "subsonic.videos";
|
||||
public static final String INTENT_EXTRA_NAME_SHOW_PLAYER = "subsonic.showplayer";
|
||||
|
||||
// Names for Intent Actions
|
||||
public static final String CMD_PROCESS_KEYCODE = "org.moire.ultrasonic.CMD_PROCESS_KEYCODE";
|
||||
@ -70,13 +69,9 @@ public final class Constants
|
||||
public static final String CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS";
|
||||
public static final String CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT";
|
||||
|
||||
// Notification IDs.
|
||||
public static final int NOTIFICATION_ID_PLAYING = 100;
|
||||
|
||||
// Preferences keys.
|
||||
public static final String PREFERENCES_KEY_SERVER_INSTANCE = "serverInstanceId";
|
||||
public static final String PREFERENCES_KEY_SERVERS_KEY = "serversKey";
|
||||
public static final String PREFERENCES_KEY_INSTALL_TIME = "installTime";
|
||||
public static final String PREFERENCES_KEY_SERVERS_EDIT = "editServers";
|
||||
public static final String PREFERENCES_KEY_THEME = "theme";
|
||||
public static final String PREFERENCES_KEY_THEME_LIGHT = "light";
|
||||
public static final String PREFERENCES_KEY_THEME_DARK = "dark";
|
||||
@ -145,12 +140,6 @@ public final class Constants
|
||||
public static final int PREFERENCE_VALUE_A2DP = 1;
|
||||
public static final int PREFERENCE_VALUE_DISABLED = 2;
|
||||
|
||||
// Number of free trial days for non-licensed servers.
|
||||
public static final int FREE_TRIAL_DAYS = 30;
|
||||
|
||||
// URL for project donations.
|
||||
public static final String DONATION_URL = "http://www.subsonic.org/pages/premium.jsp";
|
||||
|
||||
public static final String FILENAME_DOWNLOADS_SER = "downloadstate.ser";
|
||||
|
||||
public static final String ALBUM_ART_FILE = "folder.jpeg";
|
||||
|
@ -24,10 +24,12 @@ import android.graphics.BitmapFactory;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@ -43,6 +45,8 @@ import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
@ -55,6 +59,9 @@ public class FileUtil
|
||||
private static final List<String> PLAYLIST_FILE_EXTENSIONS = Collections.singletonList("m3u");
|
||||
private static final Pattern TITLE_WITH_TRACK = Pattern.compile("^\\d\\d-.*");
|
||||
|
||||
private static final Lazy<ImageLoaderProvider> imageLoaderProvider = inject(ImageLoaderProvider.class);
|
||||
private static final Lazy<PermissionUtil> permissionUtil = inject(PermissionUtil.class);
|
||||
|
||||
public static File getSongFile(Context context, MusicDirectory.Entry song)
|
||||
{
|
||||
File dir = getAlbumDirectory(context, song);
|
||||
@ -148,18 +155,12 @@ public class FileUtil
|
||||
|
||||
File avatarFile = getAvatarFile(context, username);
|
||||
|
||||
SubsonicTabActivity subsonicTabActivity = SubsonicTabActivity.getInstance();
|
||||
Bitmap bitmap = null;
|
||||
ImageLoader imageLoader = null;
|
||||
ImageLoader imageLoader = imageLoaderProvider.getValue().getImageLoader();
|
||||
|
||||
if (subsonicTabActivity != null)
|
||||
if (imageLoader != null)
|
||||
{
|
||||
imageLoader = subsonicTabActivity.getImageLoader();
|
||||
|
||||
if (imageLoader != null)
|
||||
{
|
||||
bitmap = imageLoader.getImageBitmap(username, size);
|
||||
}
|
||||
bitmap = imageLoader.getImageBitmap(username, size);
|
||||
}
|
||||
|
||||
if (bitmap != null)
|
||||
@ -218,18 +219,12 @@ public class FileUtil
|
||||
|
||||
File albumArtFile = getAlbumArtFile(context, entry);
|
||||
|
||||
SubsonicTabActivity subsonicTabActivity = SubsonicTabActivity.getInstance();
|
||||
Bitmap bitmap = null;
|
||||
ImageLoader imageLoader = null;
|
||||
ImageLoader imageLoader = imageLoaderProvider.getValue().getImageLoader();
|
||||
|
||||
if (subsonicTabActivity != null)
|
||||
if (imageLoader != null)
|
||||
{
|
||||
imageLoader = subsonicTabActivity.getImageLoader();
|
||||
|
||||
if (imageLoader != null)
|
||||
{
|
||||
bitmap = imageLoader.getImageBitmap(entry, true, size);
|
||||
}
|
||||
bitmap = imageLoader.getImageBitmap(entry, true, size);
|
||||
}
|
||||
|
||||
if (bitmap != null)
|
||||
@ -389,7 +384,7 @@ public class FileUtil
|
||||
File dir = new File(path);
|
||||
|
||||
boolean hasAccess = ensureDirectoryExistsAndIsReadWritable(dir);
|
||||
if (hasAccess == false) PermissionUtil.handlePermissionFailed(context, null);
|
||||
if (!hasAccess) permissionUtil.getValue().handlePermissionFailed(null);
|
||||
|
||||
return hasAccess ? dir : defaultMusicDirectory;
|
||||
}
|
||||
@ -492,10 +487,10 @@ public class FileUtil
|
||||
if (files == null)
|
||||
{
|
||||
Timber.w("Failed to list children for %s", dir.getPath());
|
||||
return new TreeSet<File>();
|
||||
return new TreeSet<>();
|
||||
}
|
||||
|
||||
return new TreeSet<File>(Arrays.asList(files));
|
||||
return new TreeSet<>(Arrays.asList(files));
|
||||
}
|
||||
|
||||
public static SortedSet<File> listMediaFiles(File dir)
|
||||
|
@ -1,22 +1,26 @@
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
* @version $Id$
|
||||
*/
|
||||
public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
public abstract class FragmentBackgroundTask<T> extends BackgroundTask<T>
|
||||
{
|
||||
|
||||
private final SubsonicTabActivity tabActivity;
|
||||
private final boolean changeProgress;
|
||||
private final SwipeRefreshLayout swipe;
|
||||
private final CancellationToken cancel;
|
||||
|
||||
public TabActivityBackgroundTask(SubsonicTabActivity activity, boolean changeProgress)
|
||||
public FragmentBackgroundTask(Activity activity, boolean changeProgress,
|
||||
SwipeRefreshLayout swipe, CancellationToken cancel)
|
||||
{
|
||||
super(activity);
|
||||
tabActivity = activity;
|
||||
this.changeProgress = changeProgress;
|
||||
this.swipe = swipe;
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -24,7 +28,7 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
{
|
||||
if (changeProgress)
|
||||
{
|
||||
tabActivity.setProgressVisible(true);
|
||||
if (swipe != null) swipe.setRefreshing(true);
|
||||
}
|
||||
|
||||
new Thread()
|
||||
@ -35,7 +39,7 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
try
|
||||
{
|
||||
final T result = doInBackground();
|
||||
if (isCancelled())
|
||||
if (cancel.isCancellationRequested())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -47,7 +51,7 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
{
|
||||
if (changeProgress)
|
||||
{
|
||||
tabActivity.setProgressVisible(false);
|
||||
if (swipe != null) swipe.setRefreshing(false);
|
||||
}
|
||||
|
||||
done(result);
|
||||
@ -56,7 +60,7 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
}
|
||||
catch (final Throwable t)
|
||||
{
|
||||
if (isCancelled())
|
||||
if (cancel.isCancellationRequested())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -67,7 +71,7 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
{
|
||||
if (changeProgress)
|
||||
{
|
||||
tabActivity.setProgressVisible(false);
|
||||
if (swipe != null) swipe.setRefreshing(false);
|
||||
}
|
||||
|
||||
error(t);
|
||||
@ -78,21 +82,8 @@ public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
|
||||
}.start();
|
||||
}
|
||||
|
||||
private boolean isCancelled()
|
||||
{
|
||||
return tabActivity.getIsDestroyed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(final String message)
|
||||
{
|
||||
getHandler().post(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
tabActivity.updateProgress(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@ -30,6 +29,9 @@ import timber.log.Timber;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
@ -38,8 +40,7 @@ import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@ -58,9 +59,9 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
private final int imageSizeLarge;
|
||||
private Bitmap largeUnknownImage;
|
||||
private Bitmap unknownAvatarImage;
|
||||
private Context context;
|
||||
private final Context context;
|
||||
private Collection<Thread> threads;
|
||||
private AtomicBoolean running = new AtomicBoolean();
|
||||
private final AtomicBoolean running = new AtomicBoolean();
|
||||
private int concurrency;
|
||||
|
||||
public LegacyImageLoader(
|
||||
@ -71,8 +72,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
this.concurrency = concurrency;
|
||||
queue = new LinkedBlockingQueue<>(1000);
|
||||
|
||||
Resources resources = context.getResources();
|
||||
Drawable drawable = resources.getDrawable(R.drawable.unknown_album);
|
||||
Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.unknown_album, null);
|
||||
|
||||
// Determine the density-dependent image sizes.
|
||||
if (drawable != null) {
|
||||
@ -101,7 +101,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
threads = Collections.synchronizedCollection(new ArrayList<Thread>(this.concurrency));
|
||||
|
||||
for (int i = 0; i < this.concurrency; i++) {
|
||||
Thread thread = new Thread(this, String.format("ImageLoader_%d", i));
|
||||
Thread thread = new Thread(this, String.format(Locale.US, "ImageLoader_%d", i));
|
||||
threads.add(thread);
|
||||
thread.start();
|
||||
}
|
||||
@ -120,7 +120,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
}
|
||||
|
||||
private void createLargeUnknownImage(Context context) {
|
||||
Drawable drawable = context.getResources().getDrawable(R.drawable.unknown_album);
|
||||
Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.unknown_album, null);
|
||||
Timber.i("createLargeUnknownImage");
|
||||
|
||||
if (drawable != null) {
|
||||
@ -129,8 +129,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
}
|
||||
|
||||
private void createUnknownAvatarImage(Context context) {
|
||||
Resources res = context.getResources();
|
||||
Drawable contact = res.getDrawable(R.drawable.ic_contact_picture);
|
||||
Drawable contact = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_contact_picture, null);
|
||||
unknownAvatarImage = Util.createBitmapFromDrawable(contact);
|
||||
}
|
||||
|
||||
@ -215,7 +214,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
}
|
||||
|
||||
private static String getKey(String coverArtId, int size) {
|
||||
return String.format("%s:%d", coverArtId, size);
|
||||
return String.format(Locale.US, "%s:%d", coverArtId, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -340,10 +339,6 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
setAvatarImageBitmap(view, null, unknownAvatarImage, false);
|
||||
}
|
||||
|
||||
private void setUnknownImage(View view, boolean large) {
|
||||
setUnknownImage(view, large, -1);
|
||||
}
|
||||
|
||||
private void setUnknownImage(View view, boolean large, int resId) {
|
||||
if (resId == -1) resId = R.drawable.unknown_album;
|
||||
if (large) {
|
||||
@ -424,8 +419,8 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
||||
final boolean isAvatar = this.username != null && this.entry == null;
|
||||
final Bitmap bitmap = this.entry != null ?
|
||||
musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality, null) :
|
||||
musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality, null);
|
||||
musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality) :
|
||||
musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality);
|
||||
|
||||
if (bitmap == null) {
|
||||
Timber.d("Found empty album art.");
|
||||
|
@ -1,11 +1,7 @@
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import android.app.Activity;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
@ -13,29 +9,20 @@ import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
*/
|
||||
public abstract class LoadingTask<T> extends BackgroundTask<T>
|
||||
{
|
||||
private final SubsonicTabActivity tabActivity;
|
||||
private final boolean cancellable;
|
||||
private boolean cancelled;
|
||||
private final SwipeRefreshLayout swipe;
|
||||
private final CancellationToken cancel;
|
||||
|
||||
public LoadingTask(SubsonicTabActivity activity, final boolean cancellable)
|
||||
public LoadingTask(Activity activity, SwipeRefreshLayout swipe, CancellationToken cancel)
|
||||
{
|
||||
super(activity);
|
||||
tabActivity = activity;
|
||||
this.cancellable = cancellable;
|
||||
this.swipe = swipe;
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
final ProgressDialog loading = ProgressDialog.show(tabActivity, "", "Loading. Please Wait...", true, cancellable, new DialogInterface.OnCancelListener()
|
||||
{
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog)
|
||||
{
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
});
|
||||
swipe.setRefreshing(true);
|
||||
|
||||
new Thread()
|
||||
{
|
||||
@ -45,7 +32,7 @@ public abstract class LoadingTask<T> extends BackgroundTask<T>
|
||||
try
|
||||
{
|
||||
final T result = doInBackground();
|
||||
if (isCancelled())
|
||||
if (cancel.isCancellationRequested())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -55,14 +42,14 @@ public abstract class LoadingTask<T> extends BackgroundTask<T>
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
loading.cancel();
|
||||
swipe.setRefreshing(false);
|
||||
done(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (final Throwable t)
|
||||
{
|
||||
if (isCancelled())
|
||||
if (cancel.isCancellationRequested())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -72,7 +59,7 @@ public abstract class LoadingTask<T> extends BackgroundTask<T>
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
loading.cancel();
|
||||
swipe.setRefreshing(false);
|
||||
error(t);
|
||||
}
|
||||
});
|
||||
@ -81,22 +68,8 @@ public abstract class LoadingTask<T> extends BackgroundTask<T>
|
||||
}.start();
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private boolean isCancelled()
|
||||
{
|
||||
return Build.VERSION.SDK_INT >= 17 ? tabActivity.isDestroyed() || cancelled : cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(final String message)
|
||||
{
|
||||
getHandler().post(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import com.karumi.dexter.listener.PermissionRequestErrorListener;
|
||||
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.MainActivity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -32,43 +31,59 @@ import static androidx.core.content.PermissionChecker.PERMISSION_DENIED;
|
||||
* Contains static functions for Permission handling
|
||||
*/
|
||||
public class PermissionUtil {
|
||||
|
||||
private Context activityContext;
|
||||
private final Context applicationContext;
|
||||
|
||||
public PermissionUtil(Context context) {
|
||||
applicationContext = context;
|
||||
}
|
||||
|
||||
public interface PermissionRequestFinishedCallback {
|
||||
void onPermissionRequestFinished(boolean hasPermission);
|
||||
}
|
||||
|
||||
public void ForegroundApplicationStarted(Context context) {
|
||||
this.activityContext = context;
|
||||
}
|
||||
|
||||
public void ForegroundApplicationStopped() {
|
||||
activityContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function can be used to handle file access permission failures.
|
||||
*
|
||||
* It will check if the failure is because the necessary permissions aren't available,
|
||||
* and it will request them, if necessary.
|
||||
*
|
||||
* @param context context for the operation
|
||||
* @param callback callback function to execute after the permission request is finished
|
||||
*/
|
||||
public static void handlePermissionFailed(final Context context, final PermissionRequestFinishedCallback callback) {
|
||||
|
||||
String currentCachePath = Util.getPreferences(context).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(context).getPath());
|
||||
String defaultCachePath = FileUtil.getDefaultMusicDirectory(context).getPath();
|
||||
public void handlePermissionFailed(final PermissionRequestFinishedCallback callback) {
|
||||
String currentCachePath = Util.getPreferences(applicationContext).getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory(applicationContext).getPath());
|
||||
String defaultCachePath = FileUtil.getDefaultMusicDirectory(applicationContext).getPath();
|
||||
|
||||
// Ultrasonic can do nothing about this error when the Music Directory is already set to the default.
|
||||
if (currentCachePath.compareTo(defaultCachePath) == 0) return;
|
||||
|
||||
// We must get the context of the Main Activity for the dialogs, as this function may be called from a background thread where displaying dialogs is not available
|
||||
final Context mainContext = MainActivity.getInstance();
|
||||
|
||||
if ((PermissionChecker.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_DENIED) ||
|
||||
(PermissionChecker.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PERMISSION_DENIED)) {
|
||||
if ((PermissionChecker.checkSelfPermission(applicationContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_DENIED) ||
|
||||
(PermissionChecker.checkSelfPermission(applicationContext, Manifest.permission.READ_EXTERNAL_STORAGE) == PERMISSION_DENIED)) {
|
||||
// While we request permission, the Music Directory is temporarily reset to its default location
|
||||
setCacheLocation(context, FileUtil.getDefaultMusicDirectory(context).getPath());
|
||||
requestFailedPermission(mainContext, currentCachePath, callback);
|
||||
setCacheLocation(applicationContext, FileUtil.getDefaultMusicDirectory(applicationContext).getPath());
|
||||
// If the application is not running, we can't notify the user
|
||||
if (activityContext == null) return;
|
||||
requestFailedPermission(activityContext, currentCachePath, callback);
|
||||
} else {
|
||||
setCacheLocation(context, FileUtil.getDefaultMusicDirectory(context).getPath());
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showWarning(mainContext, context.getString(R.string.permissions_message_box_title), context.getString(R.string.permissions_access_error), null);
|
||||
}
|
||||
});
|
||||
setCacheLocation(applicationContext, FileUtil.getDefaultMusicDirectory(applicationContext).getPath());
|
||||
// If the application is not running, we can't notify the user
|
||||
if (activityContext != null) {
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showWarning(activityContext, activityContext.getString(R.string.permissions_message_box_title), activityContext.getString(R.string.permissions_access_error), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
callback.onPermissionRequestFinished(false);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ public class ShufflePlayBuffer
|
||||
private static final int CAPACITY = 50;
|
||||
private static final int REFILL_THRESHOLD = 40;
|
||||
|
||||
private final List<MusicDirectory.Entry> buffer = new ArrayList<MusicDirectory.Entry>();
|
||||
private final List<MusicDirectory.Entry> buffer = new ArrayList<>();
|
||||
private final Context context;
|
||||
private ScheduledExecutorService executorService;
|
||||
private int currentServer;
|
||||
@ -78,7 +78,7 @@ public class ShufflePlayBuffer
|
||||
{
|
||||
clearBufferIfNecessary();
|
||||
|
||||
List<MusicDirectory.Entry> result = new ArrayList<MusicDirectory.Entry>(size);
|
||||
List<MusicDirectory.Entry> result = new ArrayList<>(size);
|
||||
synchronized (buffer)
|
||||
{
|
||||
while (!buffer.isEmpty() && result.size() < size)
|
||||
@ -106,7 +106,7 @@ public class ShufflePlayBuffer
|
||||
{
|
||||
MusicService service = MusicServiceFactory.getMusicService(context);
|
||||
int n = CAPACITY - buffer.size();
|
||||
MusicDirectory songs = service.getRandomSongs(n, context, null);
|
||||
MusicDirectory songs = service.getRandomSongs(n, context);
|
||||
|
||||
synchronized (buffer)
|
||||
{
|
||||
|
@ -1,22 +1,16 @@
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.DialogPreference;
|
||||
import org.moire.ultrasonic.R;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by Joshua Bahnsen on 12/22/13.
|
||||
*/
|
||||
public class TimeSpanPreference extends DialogPreference
|
||||
{
|
||||
private static final Pattern COMPILE = Pattern.compile(":");
|
||||
Context context;
|
||||
TimeSpanPicker picker;
|
||||
|
||||
public TimeSpanPreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
@ -27,6 +21,7 @@ public class TimeSpanPreference extends DialogPreference
|
||||
setNegativeButtonText(android.R.string.cancel);
|
||||
|
||||
setDialogIcon(null);
|
||||
|
||||
}
|
||||
|
||||
public String getText()
|
||||
@ -40,59 +35,4 @@ public class TimeSpanPreference extends DialogPreference
|
||||
|
||||
return this.context.getResources().getString(R.string.time_span_disabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateDialogView()
|
||||
{
|
||||
picker = new TimeSpanPicker(this.context);
|
||||
picker.setTimeSpanDisableText(this.context.getResources().getString(R.string.no_expiration));
|
||||
|
||||
String persisted = getPersistedString("");
|
||||
|
||||
if (!"".equals(persisted))
|
||||
{
|
||||
String[] split = COMPILE.split(persisted);
|
||||
|
||||
if (split.length == 2)
|
||||
{
|
||||
String amount = split[0];
|
||||
|
||||
if ("0".equals(amount) || "".equals(amount))
|
||||
{
|
||||
picker.setTimeSpanDisableCheckboxChecked(true);
|
||||
}
|
||||
|
||||
picker.setTimeSpanAmount(amount);
|
||||
picker.setTimeSpanType(split[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
picker.setTimeSpanDisableCheckboxChecked(true);
|
||||
}
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
super.onDialogClosed(positiveResult);
|
||||
|
||||
String persisted = "";
|
||||
|
||||
if (picker.getTimeSpanEnabled())
|
||||
{
|
||||
int tsAmount = picker.getTimeSpanAmount();
|
||||
|
||||
if (tsAmount > 0)
|
||||
{
|
||||
String tsType = picker.getTimeSpanType();
|
||||
|
||||
persisted = String.format("%d:%s", tsAmount, tsType);
|
||||
}
|
||||
}
|
||||
|
||||
persistString(persisted);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.DialogPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
import org.moire.ultrasonic.R;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by Joshua Bahnsen on 12/22/13.
|
||||
*/
|
||||
public class TimeSpanPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements DialogPreference.TargetFragment
|
||||
{
|
||||
private static final Pattern COMPILE = Pattern.compile(":");
|
||||
Context context;
|
||||
TimeSpanPicker picker;
|
||||
|
||||
@Override
|
||||
protected View onCreateDialogView(Context context) {
|
||||
picker = new TimeSpanPicker(context);
|
||||
this.context = context;
|
||||
|
||||
picker.setTimeSpanDisableText(this.context.getResources().getString(R.string.no_expiration));
|
||||
|
||||
Preference preference = getPreference();
|
||||
String persisted = preference.getSharedPreferences().getString(preference.getKey(), "");
|
||||
|
||||
if (!"".equals(persisted))
|
||||
{
|
||||
String[] split = COMPILE.split(persisted);
|
||||
|
||||
if (split.length == 2)
|
||||
{
|
||||
String amount = split[0];
|
||||
|
||||
if ("0".equals(amount) || "".equals(amount))
|
||||
{
|
||||
picker.setTimeSpanDisableCheckboxChecked(true);
|
||||
}
|
||||
|
||||
picker.setTimeSpanAmount(amount);
|
||||
picker.setTimeSpanType(split[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
picker.setTimeSpanDisableCheckboxChecked(true);
|
||||
}
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
String persisted = "";
|
||||
|
||||
if (picker.getTimeSpanEnabled())
|
||||
{
|
||||
int tsAmount = picker.getTimeSpanAmount();
|
||||
|
||||
if (tsAmount > 0)
|
||||
{
|
||||
String tsType = picker.getTimeSpanType();
|
||||
|
||||
persisted = String.format(Locale.US, "%d:%s", tsAmount, tsType);
|
||||
}
|
||||
}
|
||||
|
||||
Preference preference = getPreference();
|
||||
preference.getSharedPreferences().edit().putString(preference.getKey(), persisted).apply();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Preference findPreference(@NonNull CharSequence key) {
|
||||
return getPreference();
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
@ -39,21 +40,21 @@ import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.DisplayMetrics;
|
||||
import timber.log.Timber;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.DownloadActivity;
|
||||
import org.moire.ultrasonic.activity.MainActivity;
|
||||
import org.moire.ultrasonic.activity.SettingsActivity;
|
||||
import org.moire.ultrasonic.activity.NavigationActivity;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.*;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
@ -63,10 +64,8 @@ import org.moire.ultrasonic.service.DownloadFile;
|
||||
import java.io.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -95,8 +94,6 @@ public class Util
|
||||
private static boolean mediaButtonsRegisteredForUI;
|
||||
private static boolean mediaButtonsRegisteredForService;
|
||||
|
||||
private static final Map<Integer, Version> SERVER_REST_VERSIONS = new ConcurrentHashMap<Integer, Version>();
|
||||
|
||||
// Used by hexEncode()
|
||||
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
private static Toast toast;
|
||||
@ -124,7 +121,7 @@ public class Util
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_REPEAT_MODE, repeatMode.name());
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean isNotificationEnabled(Context context)
|
||||
@ -143,6 +140,7 @@ public class Util
|
||||
return preferences.getBoolean(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"BooleanMethodIsAlwaysInverted"}) // It is inverted for readability
|
||||
public static boolean isLockScreenEnabled(Context context)
|
||||
{
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
@ -207,25 +205,6 @@ public class Util
|
||||
return PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public static int getRemainingTrialDays(Context context)
|
||||
{
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
long installTime = preferences.getLong(Constants.PREFERENCES_KEY_INSTALL_TIME, 0L);
|
||||
|
||||
if (installTime == 0L)
|
||||
{
|
||||
installTime = System.currentTimeMillis();
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putLong(Constants.PREFERENCES_KEY_INSTALL_TIME, installTime);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long millisPerDay = 24L * 60L * 60L * 1000L;
|
||||
int daysSinceInstall = (int) ((now - installTime) / millisPerDay);
|
||||
return Math.max(0, Constants.FREE_TRIAL_DAYS - daysSinceInstall);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
|
||||
* <p/>
|
||||
@ -348,6 +327,7 @@ public class Util
|
||||
toast(context, message, true);
|
||||
}
|
||||
|
||||
@SuppressLint("ShowToast") // Invalid warning
|
||||
public static void toast(Context context, CharSequence message, boolean shortDuration)
|
||||
{
|
||||
if (toast == null)
|
||||
@ -383,22 +363,19 @@ public class Util
|
||||
// More than 1 GB?
|
||||
if (byteCount >= 1024 * 1024 * 1024)
|
||||
{
|
||||
NumberFormat gigaByteFormat = GIGA_BYTE_FORMAT;
|
||||
return gigaByteFormat.format((double) byteCount / (1024 * 1024 * 1024));
|
||||
return GIGA_BYTE_FORMAT.format((double) byteCount / (1024 * 1024 * 1024));
|
||||
}
|
||||
|
||||
// More than 1 MB?
|
||||
if (byteCount >= 1024 * 1024)
|
||||
{
|
||||
NumberFormat megaByteFormat = MEGA_BYTE_FORMAT;
|
||||
return megaByteFormat.format((double) byteCount / (1024 * 1024));
|
||||
return MEGA_BYTE_FORMAT.format((double) byteCount / (1024 * 1024));
|
||||
}
|
||||
|
||||
// More than 1 KB?
|
||||
if (byteCount >= 1024)
|
||||
{
|
||||
NumberFormat kiloByteFormat = KILO_BYTE_FORMAT;
|
||||
return kiloByteFormat.format((double) byteCount / 1024);
|
||||
return KILO_BYTE_FORMAT.format((double) byteCount / 1024);
|
||||
}
|
||||
|
||||
return byteCount + " B";
|
||||
@ -604,19 +581,6 @@ public class Util
|
||||
showDialog(context, android.R.drawable.ic_dialog_info, titleId, messageId);
|
||||
}
|
||||
|
||||
public static void showWelcomeDialog(final Context context, final MainActivity activity, int titleId, int messageId)
|
||||
{
|
||||
new AlertDialog.Builder(context).setIcon(android.R.drawable.ic_dialog_info).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
dialog.dismiss();
|
||||
activity.startActivityForResultWithoutTransition(activity, SettingsActivity.class);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private static void showDialog(Context context, int icon, int titleId, int messageId)
|
||||
{
|
||||
new AlertDialog.Builder(context).setIcon(icon).setTitle(titleId).setMessage(messageId).setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener()
|
||||
@ -642,11 +606,6 @@ public class Util
|
||||
}
|
||||
}
|
||||
|
||||
public static void disablePendingTransition(Activity activity)
|
||||
{
|
||||
activity.overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
public static Drawable getDrawableFromAttribute(Context context, int attr)
|
||||
{
|
||||
int[] attrs = new int[]{attr};
|
||||
@ -682,7 +641,7 @@ public class Util
|
||||
|
||||
public static WifiManager.WifiLock createWifiLock(Context context, String tag)
|
||||
{
|
||||
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||
WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
return wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, tag);
|
||||
}
|
||||
|
||||
@ -917,11 +876,7 @@ public class Util
|
||||
avrcpIntent.putExtra("playing", true);
|
||||
break;
|
||||
case STOPPED:
|
||||
avrcpIntent.putExtra("playing", false);
|
||||
break;
|
||||
case PAUSED:
|
||||
avrcpIntent.putExtra("playing", false);
|
||||
break;
|
||||
case COMPLETED:
|
||||
avrcpIntent.putExtra("playing", false);
|
||||
break;
|
||||
@ -1026,7 +981,7 @@ public class Util
|
||||
// guarantee
|
||||
// a final image with both dimensions larger than or equal to the
|
||||
// requested height and width.
|
||||
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
|
||||
inSampleSize = Math.min(heightRatio, widthRatio);
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
@ -1034,8 +989,10 @@ public class Util
|
||||
|
||||
public static void linkButtons(Context context, RemoteViews views, boolean playerActive)
|
||||
{
|
||||
Intent intent = new Intent(context, NavigationActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
if (playerActive)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, true);
|
||||
|
||||
Intent intent = new Intent(context, playerActive ? DownloadActivity.class : MainActivity.class);
|
||||
intent.setAction("android.intent.action.MAIN");
|
||||
intent.addCategory("android.intent.category.LAUNCHER");
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
@ -1098,6 +1055,7 @@ public class Util
|
||||
views.setOnClickPendingIntent(R.id.notification_five_star_5, pendingIntent);
|
||||
}
|
||||
|
||||
// TODO: Shouldn't this be used when making requests?
|
||||
public static int getNetworkTimeout(Context context)
|
||||
{
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
@ -1203,6 +1161,7 @@ public class Util
|
||||
return Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME, "300"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted") // Inverted for readability
|
||||
public static boolean isNullOrWhiteSpace(String string)
|
||||
{
|
||||
return string == null || string.isEmpty() || string.trim().isEmpty();
|
||||
@ -1252,18 +1211,18 @@ public class Util
|
||||
|
||||
if (hours >= 10)
|
||||
{
|
||||
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
|
||||
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
else if (hours > 0)
|
||||
{
|
||||
return String.format("%d:%02d:%02d", hours, minutes, seconds);
|
||||
return String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, seconds);
|
||||
}
|
||||
else if (minutes >= 10)
|
||||
{
|
||||
return String.format("%02d:%02d", minutes, seconds);
|
||||
return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
|
||||
}
|
||||
|
||||
else return minutes > 0 ? String.format("%d:%02d", minutes, seconds) : String.format("0:%02d", seconds);
|
||||
else return minutes > 0 ? String.format(Locale.getDefault(), "%d:%02d", minutes, seconds) : String.format(Locale.getDefault(), "0:%02d", seconds);
|
||||
}
|
||||
|
||||
public static VideoPlayerType getVideoPlayerType(Context context)
|
||||
@ -1342,6 +1301,7 @@ public class Util
|
||||
return versionCode;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted") // Inverted for readability
|
||||
public static boolean getShouldSendBluetoothNotifications(Context context)
|
||||
{
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
@ -1409,7 +1369,7 @@ public class Util
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putBoolean(Constants.PREFERENCES_KEY_ASK_FOR_SHARE_DETAILS, shouldAskForShareDetails);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void setDefaultShareExpiration(Context context, String defaultShareExpiration)
|
||||
@ -1417,7 +1377,7 @@ public class Util
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION, defaultShareExpiration);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void setDefaultShareDescription(Context context, String defaultShareDescription)
|
||||
@ -1425,7 +1385,7 @@ public class Util
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION, defaultShareDescription);
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean getShouldShowAllSongsByArtist(Context context)
|
||||
@ -1498,4 +1458,12 @@ public class Util
|
||||
return preferences.getBoolean(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE, false);
|
||||
}
|
||||
|
||||
public static void hideKeyboard(Activity activity) {
|
||||
InputMethodManager inputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
View currentFocusedView = activity.getCurrentFocus();
|
||||
if (currentFocusedView != null) {
|
||||
inputManager.hideSoftInputFromWindow(currentFocusedView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,8 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@ -38,27 +38,27 @@ public enum VideoPlayerType
|
||||
MX("mx")
|
||||
{
|
||||
@Override
|
||||
public void playVideo(final Activity activity, MusicDirectory.Entry entry) throws Exception
|
||||
public void playVideo(final Context context, MusicDirectory.Entry entry) throws Exception
|
||||
{
|
||||
|
||||
// Check if MX Player is installed.
|
||||
boolean installedAd = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_AD);
|
||||
boolean installedPro = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_PRO);
|
||||
boolean installedAd = Util.isPackageInstalled(context, PACKAGE_NAME_MX_AD);
|
||||
boolean installedPro = Util.isPackageInstalled(context, PACKAGE_NAME_MX_PRO);
|
||||
|
||||
if (!installedAd && !installedPro)
|
||||
{
|
||||
new AlertDialog.Builder(activity).setMessage(R.string.video_get_mx_player_text).setPositiveButton(R.string.video_get_mx_player_button, new DialogInterface.OnClickListener()
|
||||
new AlertDialog.Builder(context).setMessage(R.string.video_get_mx_player_text).setPositiveButton(R.string.video_get_mx_player_button, new DialogInterface.OnClickListener()
|
||||
{
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int i)
|
||||
{
|
||||
try
|
||||
{
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("market://details?id=%s", PACKAGE_NAME_MX_AD))));
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("market://details?id=%s", PACKAGE_NAME_MX_AD))));
|
||||
}
|
||||
catch (android.content.ActivityNotFoundException x)
|
||||
{
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://play.google.com/store/apps/details?id=%s", PACKAGE_NAME_MX_AD))));
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://play.google.com/store/apps/details?id=%s", PACKAGE_NAME_MX_AD))));
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
@ -79,8 +79,8 @@ public enum VideoPlayerType
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setPackage(installedPro ? PACKAGE_NAME_MX_PRO : PACKAGE_NAME_MX_AD);
|
||||
intent.putExtra("title", entry.getTitle());
|
||||
intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(activity).getVideoUrl(activity, entry.getId(), false)), "video/*");
|
||||
activity.startActivity(intent);
|
||||
intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoUrl(context, entry.getId(), false)), "video/*");
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -88,22 +88,22 @@ public enum VideoPlayerType
|
||||
FLASH("flash")
|
||||
{
|
||||
@Override
|
||||
public void playVideo(Activity activity, MusicDirectory.Entry entry) throws Exception
|
||||
public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(MusicServiceFactory.getMusicService(activity).getVideoUrl(activity, entry.getId(), true)));
|
||||
activity.startActivity(intent);
|
||||
intent.setData(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoUrl(context, entry.getId(), true)));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
},
|
||||
|
||||
DEFAULT("default")
|
||||
{
|
||||
@Override
|
||||
public void playVideo(Activity activity, MusicDirectory.Entry entry) throws Exception
|
||||
public void playVideo(Context context, MusicDirectory.Entry entry) throws Exception
|
||||
{
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(activity).getVideoUrl(activity, entry.getId(), false)), "video/*");
|
||||
activity.startActivity(intent);
|
||||
intent.setDataAndType(Uri.parse(MusicServiceFactory.getMusicService(context).getVideoUrl(context, entry.getId(), false)), "video/*");
|
||||
context.startActivity(intent);
|
||||
}
|
||||
};
|
||||
|
||||
@ -131,7 +131,7 @@ public enum VideoPlayerType
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract void playVideo(Activity activity, MusicDirectory.Entry entry) throws Exception;
|
||||
public abstract void playVideo(Context context, MusicDirectory.Entry entry) throws Exception;
|
||||
|
||||
private static final String PACKAGE_NAME_MX_AD = "com.mxtech.videoplayer.ad";
|
||||
private static final String PACKAGE_NAME_MX_PRO = "com.mxtech.videoplayer.pro";
|
||||
|
@ -23,8 +23,6 @@ import android.graphics.drawable.Drawable;
|
||||
import timber.log.Timber;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
@ -44,10 +42,10 @@ public class AlbumView extends UpdateView
|
||||
private static Drawable starHollowDrawable;
|
||||
private static String theme;
|
||||
|
||||
private Context context;
|
||||
private final Context context;
|
||||
private MusicDirectory.Entry entry;
|
||||
private EntryAdapter.AlbumViewHolder viewHolder;
|
||||
private ImageLoader imageLoader;
|
||||
private final ImageLoader imageLoader;
|
||||
private boolean maximized = false;
|
||||
|
||||
public AlbumView(Context context, ImageLoader imageLoader)
|
||||
@ -75,10 +73,10 @@ public class AlbumView extends UpdateView
|
||||
{
|
||||
LayoutInflater.from(context).inflate(R.layout.album_list_item, this, true);
|
||||
viewHolder = new EntryAdapter.AlbumViewHolder();
|
||||
viewHolder.title = (TextView) findViewById(R.id.album_title);
|
||||
viewHolder.artist = (TextView) findViewById(R.id.album_artist);
|
||||
viewHolder.cover_art = (ImageView) findViewById(R.id.album_coverart);
|
||||
viewHolder.star = (ImageView) findViewById(R.id.album_star);
|
||||
viewHolder.title = findViewById(R.id.album_title);
|
||||
viewHolder.artist = findViewById(R.id.album_artist);
|
||||
viewHolder.cover_art = findViewById(R.id.album_coverart);
|
||||
viewHolder.star = findViewById(R.id.album_star);
|
||||
setTag(viewHolder);
|
||||
}
|
||||
|
||||
@ -99,11 +97,7 @@ public class AlbumView extends UpdateView
|
||||
}
|
||||
|
||||
public void maximizeOrMinimize() {
|
||||
if (maximized) {
|
||||
maximized = false;
|
||||
} else {
|
||||
maximized = true;
|
||||
}
|
||||
maximized = !maximized;
|
||||
if (this.viewHolder.title != null) {
|
||||
this.viewHolder.title.setSingleLine(!maximized);
|
||||
}
|
||||
@ -164,11 +158,11 @@ public class AlbumView extends UpdateView
|
||||
{
|
||||
if (!isStarred)
|
||||
{
|
||||
musicService.star(!useId3 ? id : null, useId3 ? id : null, null, getContext(), null);
|
||||
musicService.star(!useId3 ? id : null, useId3 ? id : null, null, getContext());
|
||||
}
|
||||
else
|
||||
{
|
||||
musicService.unstar(!useId3 ? id : null, useId3 ? id : null, null, getContext(), null);
|
||||
musicService.unstar(!useId3 ? id : null, useId3 ? id : null, null, getContext());
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.moire.ultrasonic.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
@ -9,11 +10,10 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider;
|
||||
import org.moire.ultrasonic.domain.ChatMessage;
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
|
||||
import org.moire.ultrasonic.util.ImageLoader;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
@ -26,18 +26,19 @@ import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
public class ChatAdapter extends ArrayAdapter<ChatMessage>
|
||||
{
|
||||
private final SubsonicTabActivity activity;
|
||||
private List<ChatMessage> messages;
|
||||
private final Context context;
|
||||
private final List<ChatMessage> messages;
|
||||
|
||||
private static final String phoneRegex = "1?\\W*([2-9][0-8][0-9])\\W*([2-9][0-9]{2})\\W*([0-9]{4})";
|
||||
private static final Pattern phoneMatcher = Pattern.compile(phoneRegex);
|
||||
|
||||
private Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
private final Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class);
|
||||
|
||||
public ChatAdapter(SubsonicTabActivity activity, List<ChatMessage> messages)
|
||||
public ChatAdapter(Context context, List<ChatMessage> messages)
|
||||
{
|
||||
super(activity, R.layout.chat_item, messages);
|
||||
this.activity = activity;
|
||||
super(context, R.layout.chat_item, messages);
|
||||
this.context = context;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
@ -91,14 +92,14 @@ public class ChatAdapter extends ArrayAdapter<ChatMessage>
|
||||
|
||||
holder.chatMessage = message;
|
||||
|
||||
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(activity);
|
||||
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context);
|
||||
String messageTimeFormatted = String.format("[%s]", timeFormat.format(messageTime));
|
||||
|
||||
ImageLoader imageLoader = activity.getImageLoader();
|
||||
ImageLoader imageLoaderInstance = imageLoader.getValue().getImageLoader();
|
||||
|
||||
if (imageLoader != null)
|
||||
if (imageLoaderInstance != null)
|
||||
{
|
||||
imageLoader.loadAvatarImage(holder.avatar, messageUser, false, holder.avatar.getWidth(), false, true);
|
||||
imageLoaderInstance.loadAvatarImage(holder.avatar, messageUser, false, holder.avatar.getWidth(), false, true);
|
||||
}
|
||||
|
||||
holder.username.setText(messageUser);
|
||||
@ -110,7 +111,7 @@ public class ChatAdapter extends ArrayAdapter<ChatMessage>
|
||||
|
||||
private View inflateView(int layout, ViewGroup parent)
|
||||
{
|
||||
return LayoutInflater.from(activity).inflate(layout, parent, false);
|
||||
return LayoutInflater.from(context).inflate(layout, parent, false);
|
||||
}
|
||||
|
||||
private static ViewHolder createViewHolder(int layout, View convertView)
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.moire.ultrasonic.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
@ -25,7 +26,7 @@ import android.widget.CheckedTextView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
import org.moire.ultrasonic.util.ImageLoader;
|
||||
|
||||
@ -36,15 +37,15 @@ import java.util.List;
|
||||
*/
|
||||
public class EntryAdapter extends ArrayAdapter<Entry>
|
||||
{
|
||||
private final SubsonicTabActivity activity;
|
||||
private final Context context;
|
||||
private final ImageLoader imageLoader;
|
||||
private final boolean checkable;
|
||||
|
||||
public EntryAdapter(SubsonicTabActivity activity, ImageLoader imageLoader, List<Entry> entries, boolean checkable)
|
||||
public EntryAdapter(Context context, ImageLoader imageLoader, List<Entry> entries, boolean checkable)
|
||||
{
|
||||
super(activity, android.R.layout.simple_list_item_1, entries);
|
||||
super(context, android.R.layout.simple_list_item_1, entries);
|
||||
|
||||
this.activity = activity;
|
||||
this.context = context;
|
||||
this.imageLoader = imageLoader;
|
||||
this.checkable = checkable;
|
||||
}
|
||||
@ -58,7 +59,7 @@ public class EntryAdapter extends ArrayAdapter<Entry>
|
||||
{
|
||||
AlbumView view;
|
||||
|
||||
if (convertView != null && convertView instanceof AlbumView)
|
||||
if (convertView instanceof AlbumView)
|
||||
{
|
||||
AlbumView currentView = (AlbumView) convertView;
|
||||
|
||||
@ -75,7 +76,7 @@ public class EntryAdapter extends ArrayAdapter<Entry>
|
||||
}
|
||||
else
|
||||
{
|
||||
view = new AlbumView(activity, imageLoader);
|
||||
view = new AlbumView(context, imageLoader);
|
||||
view.setLayout();
|
||||
}
|
||||
|
||||
@ -86,7 +87,7 @@ public class EntryAdapter extends ArrayAdapter<Entry>
|
||||
{
|
||||
SongView view;
|
||||
|
||||
if (convertView != null && convertView instanceof SongView)
|
||||
if (convertView instanceof SongView)
|
||||
{
|
||||
SongView currentView = (SongView) convertView;
|
||||
|
||||
@ -104,7 +105,7 @@ public class EntryAdapter extends ArrayAdapter<Entry>
|
||||
}
|
||||
else
|
||||
{
|
||||
view = new SongView(activity);
|
||||
view = new SongView(context);
|
||||
view.setLayout(entry);
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,14 @@
|
||||
package org.moire.ultrasonic.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import org.moire.ultrasonic.domain.Playlist;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -20,12 +17,12 @@ import java.util.List;
|
||||
public class PlaylistAdapter extends ArrayAdapter<Playlist>
|
||||
{
|
||||
|
||||
private final SubsonicTabActivity activity;
|
||||
private final Context context;
|
||||
|
||||
public PlaylistAdapter(SubsonicTabActivity activity, List<Playlist> Playlists)
|
||||
public PlaylistAdapter(Context context, List<Playlist> Playlists)
|
||||
{
|
||||
super(activity, R.layout.playlist_list_item, Playlists);
|
||||
this.activity = activity;
|
||||
super(context, R.layout.playlist_list_item, Playlists);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,7 +31,7 @@ public class PlaylistAdapter extends ArrayAdapter<Playlist>
|
||||
Playlist entry = getItem(position);
|
||||
PlaylistView view;
|
||||
|
||||
if (convertView != null && convertView instanceof PlaylistView)
|
||||
if (convertView instanceof PlaylistView)
|
||||
{
|
||||
PlaylistView currentView = (PlaylistView) convertView;
|
||||
|
||||
@ -44,7 +41,7 @@ public class PlaylistAdapter extends ArrayAdapter<Playlist>
|
||||
}
|
||||
else
|
||||
{
|
||||
view = new PlaylistView(activity);
|
||||
view = new PlaylistView(context);
|
||||
view.setLayout();
|
||||
}
|
||||
|
||||
@ -52,23 +49,6 @@ public class PlaylistAdapter extends ArrayAdapter<Playlist>
|
||||
return view;
|
||||
}
|
||||
|
||||
public static class PlaylistComparator implements Comparator<Playlist>, Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -6201663557439120008L;
|
||||
|
||||
@Override
|
||||
public int compare(Playlist playlist1, Playlist playlist2)
|
||||
{
|
||||
return playlist1.getName().compareToIgnoreCase(playlist2.getName());
|
||||
}
|
||||
|
||||
public static List<Playlist> sort(List<Playlist> playlists)
|
||||
{
|
||||
Collections.sort(playlists, new PlaylistComparator());
|
||||
return playlists;
|
||||
}
|
||||
}
|
||||
|
||||
static class ViewHolder
|
||||
{
|
||||
TextView name;
|
||||
|
@ -1,17 +1,14 @@
|
||||
package org.moire.ultrasonic.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.activity.SubsonicTabActivity;
|
||||
import org.moire.ultrasonic.domain.Share;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -20,12 +17,12 @@ import java.util.List;
|
||||
public class ShareAdapter extends ArrayAdapter<Share>
|
||||
{
|
||||
|
||||
private final SubsonicTabActivity activity;
|
||||
private final Context context;
|
||||
|
||||
public ShareAdapter(SubsonicTabActivity activity, List<Share> Shares)
|
||||
public ShareAdapter(Context context, List<Share> Shares)
|
||||
{
|
||||
super(activity, R.layout.share_list_item, Shares);
|
||||
this.activity = activity;
|
||||
super(context, R.layout.share_list_item, Shares);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,7 +31,7 @@ public class ShareAdapter extends ArrayAdapter<Share>
|
||||
Share entry = getItem(position);
|
||||
ShareView view;
|
||||
|
||||
if (convertView != null && convertView instanceof ShareView)
|
||||
if (convertView instanceof ShareView)
|
||||
{
|
||||
ShareView currentView = (ShareView) convertView;
|
||||
|
||||
@ -44,7 +41,7 @@ public class ShareAdapter extends ArrayAdapter<Share>
|
||||
}
|
||||
else
|
||||
{
|
||||
view = new ShareView(activity);
|
||||
view = new ShareView(context);
|
||||
view.setLayout();
|
||||
}
|
||||
|
||||
@ -52,23 +49,6 @@ public class ShareAdapter extends ArrayAdapter<Share>
|
||||
return view;
|
||||
}
|
||||
|
||||
public static class ShareComparator implements Comparator<Share>, Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -7169409928471418921L;
|
||||
|
||||
@Override
|
||||
public int compare(Share share1, Share share2)
|
||||
{
|
||||
return share1.getId().compareToIgnoreCase(share2.getId());
|
||||
}
|
||||
|
||||
public static List<Share> sort(List<Share> shares)
|
||||
{
|
||||
Collections.sort(shares, new ShareComparator());
|
||||
return shares;
|
||||
}
|
||||
}
|
||||
|
||||
static class ViewHolder
|
||||
{
|
||||
TextView url;
|
||||
|
@ -0,0 +1,379 @@
|
||||
package org.moire.ultrasonic.activity
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.media.AudioManager
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.provider.SearchRecentSuggestions
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.onNavDestinationSelected
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.PlayerState
|
||||
import org.moire.ultrasonic.fragment.OnBackPressedHandler
|
||||
import org.moire.ultrasonic.fragment.ServerSettingsModel
|
||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider
|
||||
import org.moire.ultrasonic.service.DownloadFile
|
||||
import org.moire.ultrasonic.service.MediaPlayerController
|
||||
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.FileUtil
|
||||
import org.moire.ultrasonic.util.NowPlayingEventDistributor
|
||||
import org.moire.ultrasonic.util.NowPlayingEventListener
|
||||
import org.moire.ultrasonic.util.PermissionUtil
|
||||
import org.moire.ultrasonic.util.SubsonicUncaughtExceptionHandler
|
||||
import org.moire.ultrasonic.util.ThemeChangedEventDistributor
|
||||
import org.moire.ultrasonic.util.ThemeChangedEventListener
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* The main Activity of Ultrasonic which loads all other screens as Fragments
|
||||
*/
|
||||
class NavigationActivity : AppCompatActivity() {
|
||||
private var chatMenuItem: MenuItem? = null
|
||||
private var bookmarksMenuItem: MenuItem? = null
|
||||
private var sharesMenuItem: MenuItem? = null
|
||||
private var podcastsMenuItem: MenuItem? = null
|
||||
private var nowPlayingView: FragmentContainerView? = null
|
||||
private var nowPlayingHidden = false
|
||||
private var navigationView: NavigationView? = null
|
||||
private var drawerLayout: DrawerLayout? = null
|
||||
private var host: NavHostFragment? = null
|
||||
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
private lateinit var nowPlayingEventListener: NowPlayingEventListener
|
||||
private lateinit var themeChangedEventListener: ThemeChangedEventListener
|
||||
|
||||
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
|
||||
private val mediaPlayerController: MediaPlayerController by inject()
|
||||
private val imageLoaderProvider: ImageLoaderProvider by inject()
|
||||
private val nowPlayingEventDistributor: NowPlayingEventDistributor by inject()
|
||||
private val themeChangedEventDistributor: ThemeChangedEventDistributor by inject()
|
||||
private val permissionUtil: PermissionUtil by inject()
|
||||
|
||||
private var infoDialogDisplayed = false
|
||||
private var currentFragmentId: Int = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setUncaughtExceptionHandler()
|
||||
permissionUtil.ForegroundApplicationStarted(this)
|
||||
Util.applyTheme(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
volumeControlStream = AudioManager.STREAM_MUSIC
|
||||
setContentView(R.layout.navigation_activity)
|
||||
nowPlayingView = findViewById(R.id.now_playing_fragment)
|
||||
navigationView = findViewById(R.id.nav_view)
|
||||
drawerLayout = findViewById(R.id.drawer_layout)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
host = supportFragmentManager
|
||||
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return
|
||||
|
||||
val navController = host!!.navController
|
||||
|
||||
appBarConfiguration = AppBarConfiguration(
|
||||
setOf(
|
||||
R.id.mainFragment,
|
||||
R.id.selectArtistFragment,
|
||||
R.id.searchFragment,
|
||||
R.id.playlistsFragment,
|
||||
R.id.sharesFragment,
|
||||
R.id.bookmarksFragment,
|
||||
R.id.chatFragment,
|
||||
R.id.podcastFragment,
|
||||
R.id.settingsFragment,
|
||||
R.id.aboutFragment,
|
||||
R.id.playerFragment
|
||||
),
|
||||
drawerLayout
|
||||
)
|
||||
|
||||
setupActionBar(navController, appBarConfiguration)
|
||||
|
||||
setupNavigationMenu(navController)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
val dest: String = try {
|
||||
resources.getResourceName(destination.id)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
destination.id.toString()
|
||||
}
|
||||
Timber.d("Navigated to $dest")
|
||||
|
||||
currentFragmentId = destination.id
|
||||
// Handle the hiding of the NowPlaying fragment when the Player is active
|
||||
if (currentFragmentId == R.id.playerFragment) {
|
||||
hideNowPlaying()
|
||||
} else {
|
||||
if (!nowPlayingHidden) showNowPlaying()
|
||||
}
|
||||
|
||||
// Hides menu items for Offline mode
|
||||
setMenuForServerSetting()
|
||||
}
|
||||
|
||||
// Determine first run and migrate server settings to DB as early as possible
|
||||
var showWelcomeScreen = Util.isFirstRun(this)
|
||||
val areServersMigrated: Boolean = serverSettingsModel.migrateFromPreferences()
|
||||
|
||||
// If there are any servers in the DB, do not show the welcome screen
|
||||
showWelcomeScreen = showWelcomeScreen and !areServersMigrated
|
||||
|
||||
loadSettings()
|
||||
showInfoDialog(showWelcomeScreen)
|
||||
|
||||
nowPlayingEventListener = object : NowPlayingEventListener {
|
||||
override fun onDismissNowPlaying() {
|
||||
nowPlayingHidden = true
|
||||
hideNowPlaying()
|
||||
}
|
||||
|
||||
override fun onHideNowPlaying() {
|
||||
hideNowPlaying()
|
||||
}
|
||||
|
||||
override fun onShowNowPlaying() {
|
||||
showNowPlaying()
|
||||
}
|
||||
}
|
||||
|
||||
themeChangedEventListener = object : ThemeChangedEventListener {
|
||||
override fun onThemeChanged() { recreate() }
|
||||
}
|
||||
|
||||
nowPlayingEventDistributor.subscribe(nowPlayingEventListener)
|
||||
themeChangedEventDistributor.subscribe(themeChangedEventListener)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
setMenuForServerSetting()
|
||||
Util.registerMediaButtonEventReceiver(this, false)
|
||||
// Lifecycle support's constructor registers some event receivers so it should be created early
|
||||
lifecycleSupport.onCreate()
|
||||
|
||||
if (!nowPlayingHidden) showNowPlaying()
|
||||
else hideNowPlaying()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Util.unregisterMediaButtonEventReceiver(this, false)
|
||||
nowPlayingEventDistributor.unsubscribe(nowPlayingEventListener)
|
||||
themeChangedEventDistributor.unsubscribe(themeChangedEventListener)
|
||||
imageLoaderProvider.clearImageLoader()
|
||||
permissionUtil.ForegroundApplicationStopped()
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|
||||
val isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP
|
||||
val isVolumeAdjust = isVolumeDown || isVolumeUp
|
||||
val isJukebox = mediaPlayerController.isJukeboxEnabled
|
||||
if (isVolumeAdjust && isJukebox) {
|
||||
mediaPlayerController.adjustJukeboxVolume(isVolumeUp)
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
private fun setupNavigationMenu(navController: NavController) {
|
||||
navigationView?.setupWithNavController(navController)
|
||||
|
||||
// The exit menu is handled here manually
|
||||
val exitItem: MenuItem? = navigationView?.menu?.findItem(R.id.menu_exit)
|
||||
exitItem?.setOnMenuItemClickListener { item ->
|
||||
if (item.itemId == R.id.menu_exit) {
|
||||
setResult(Constants.RESULT_CLOSE_ALL)
|
||||
mediaPlayerController.stopJukeboxService()
|
||||
imageLoaderProvider.getImageLoader().stopImageLoader()
|
||||
finish()
|
||||
exit()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
chatMenuItem = navigationView?.menu?.findItem(R.id.chatFragment)
|
||||
bookmarksMenuItem = navigationView?.menu?.findItem(R.id.bookmarksFragment)
|
||||
sharesMenuItem = navigationView?.menu?.findItem(R.id.sharesFragment)
|
||||
podcastsMenuItem = navigationView?.menu?.findItem(R.id.podcastFragment)
|
||||
}
|
||||
|
||||
private fun setupActionBar(navController: NavController, appBarConfig: AppBarConfiguration) {
|
||||
setupActionBarWithNavController(navController, appBarConfig)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (drawerLayout?.isDrawerVisible(GravityCompat.START) == true) {
|
||||
this.drawerLayout?.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
val currentFragment = host!!.childFragmentManager.fragments.last()
|
||||
if (currentFragment is OnBackPressedHandler) currentFragment.onBackPressed()
|
||||
else super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val retValue = super.onCreateOptionsMenu(menu)
|
||||
if (navigationView == null) {
|
||||
menuInflater.inflate(R.menu.navigation, menu)
|
||||
return true
|
||||
}
|
||||
return retValue
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return item.onNavDestinationSelected(findNavController(R.id.nav_host_fragment)) ||
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
val currentFragment = host!!.childFragmentManager.fragments.last()
|
||||
return if (currentFragment is OnBackPressedHandler) {
|
||||
currentFragment.onBackPressed()
|
||||
true
|
||||
} else {
|
||||
findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Test if this works with external Intents
|
||||
// android.intent.action.SEARCH and android.media.action.MEDIA_PLAY_FROM_SEARCH calls here
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent == null) return
|
||||
|
||||
if (intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHOW_PLAYER, false)) {
|
||||
findNavController(R.id.nav_host_fragment).navigate(R.id.playerFragment)
|
||||
return
|
||||
}
|
||||
|
||||
val query = intent.getStringExtra(SearchManager.QUERY)
|
||||
|
||||
if (query != null) {
|
||||
val autoPlay = intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
|
||||
val suggestions = SearchRecentSuggestions(
|
||||
this,
|
||||
SearchSuggestionProvider.AUTHORITY, SearchSuggestionProvider.MODE
|
||||
)
|
||||
suggestions.saveRecentQuery(query, null)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_QUERY, query)
|
||||
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_AUTOPLAY, autoPlay)
|
||||
findNavController(R.id.nav_host_fragment).navigate(R.id.searchFragment, bundle)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
PreferenceManager.setDefaultValues(this, R.xml.settings, false)
|
||||
val preferences = Util.getPreferences(this)
|
||||
if (!preferences.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION)) {
|
||||
val editor = preferences.edit()
|
||||
editor.putString(
|
||||
Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
||||
FileUtil.getDefaultMusicDirectory(this).path
|
||||
)
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun exit() {
|
||||
lifecycleSupport.onDestroy()
|
||||
Util.unregisterMediaButtonEventReceiver(this, false)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun showInfoDialog(show: Boolean) {
|
||||
if (!infoDialogDisplayed) {
|
||||
infoDialogDisplayed = true
|
||||
if (show) {
|
||||
AlertDialog.Builder(this)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setTitle(R.string.main_welcome_title)
|
||||
.setMessage(R.string.main_welcome_text)
|
||||
.setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUncaughtExceptionHandler() {
|
||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
if (handler !is SubsonicUncaughtExceptionHandler) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(SubsonicUncaughtExceptionHandler(this))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNowPlaying() {
|
||||
if (!Util.getShowNowPlayingPreference(this)) {
|
||||
hideNowPlaying()
|
||||
return
|
||||
}
|
||||
|
||||
// The logic for nowPlayingHidden is that the user can dismiss NowPlaying with a gesture,
|
||||
// and when the MediaPlayerService requests that it should be shown, it returns
|
||||
nowPlayingHidden = false
|
||||
// Do not show for Player fragment
|
||||
if (currentFragmentId == R.id.playerFragment) {
|
||||
hideNowPlaying()
|
||||
return
|
||||
}
|
||||
|
||||
if (nowPlayingView != null) {
|
||||
val playerState: PlayerState = mediaPlayerController.playerState
|
||||
if (playerState == PlayerState.PAUSED || playerState == PlayerState.STARTED) {
|
||||
val file: DownloadFile? = mediaPlayerController.currentPlaying
|
||||
if (file != null) {
|
||||
nowPlayingView?.visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
hideNowPlaying()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideNowPlaying() {
|
||||
nowPlayingView?.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun setMenuForServerSetting() {
|
||||
val visibility = !isOffline(this)
|
||||
chatMenuItem?.isVisible = visibility
|
||||
bookmarksMenuItem?.isVisible = visibility
|
||||
sharesMenuItem?.isVisible = visibility
|
||||
podcastsMenuItem?.isVisible = visibility
|
||||
}
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
/*
|
||||
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 2020 (C) Jozsef Varga
|
||||
*/
|
||||
package org.moire.ultrasonic.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.PopupMenu
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.Artist
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
/**
|
||||
* Displays the available Artists in a list
|
||||
*/
|
||||
class SelectArtistActivity : SubsonicTabActivity() {
|
||||
private val activeServerProvider: ActiveServerProvider by inject()
|
||||
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||
private val artistListModel: ArtistListModel by viewModel()
|
||||
|
||||
private var refreshArtistListView: SwipeRefreshLayout? = null
|
||||
private var artistListView: RecyclerView? = null
|
||||
private var musicFolders: List<MusicFolder>? = null
|
||||
private lateinit var viewManager: RecyclerView.LayoutManager
|
||||
private lateinit var viewAdapter: ArtistRowAdapter
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.select_artist)
|
||||
|
||||
refreshArtistListView = findViewById(R.id.select_artist_refresh)
|
||||
refreshArtistListView!!.setOnRefreshListener {
|
||||
artistListModel.refresh(refreshArtistListView!!)
|
||||
}
|
||||
|
||||
val shouldShowHeader = (!isOffline(this) && !Util.getShouldUseId3Tags(this))
|
||||
|
||||
val title = intent.getStringExtra(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE)
|
||||
if (title == null) {
|
||||
setActionBarSubtitle(
|
||||
if (isOffline(this)) R.string.music_library_label_offline
|
||||
else R.string.music_library_label
|
||||
)
|
||||
} else {
|
||||
actionBarSubtitle = title
|
||||
}
|
||||
val browseMenuItem = findViewById<View>(R.id.menu_browse)
|
||||
menuDrawer.setActiveView(browseMenuItem)
|
||||
musicFolders = null
|
||||
|
||||
val refresh = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false)
|
||||
|
||||
artistListModel.getMusicFolders()
|
||||
.observe(
|
||||
this,
|
||||
Observer { changedFolders ->
|
||||
if (changedFolders != null) {
|
||||
musicFolders = changedFolders
|
||||
viewAdapter.setFolderName(getMusicFolderName(changedFolders))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val artists = artistListModel.getArtists(refresh, refreshArtistListView!!)
|
||||
artists.observe(
|
||||
this, Observer { changedArtists -> viewAdapter.setData(changedArtists) }
|
||||
)
|
||||
|
||||
viewManager = LinearLayoutManager(this)
|
||||
viewAdapter = ArtistRowAdapter(
|
||||
artists.value ?: listOf(),
|
||||
getText(R.string.select_artist_all_folders).toString(),
|
||||
shouldShowHeader,
|
||||
{ artist -> onItemClick(artist) },
|
||||
{ menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) },
|
||||
{ view -> onFolderClick(view) },
|
||||
imageLoader
|
||||
)
|
||||
|
||||
artistListView = findViewById<RecyclerView>(R.id.select_artist_list).apply {
|
||||
setHasFixedSize(true)
|
||||
layoutManager = viewManager
|
||||
adapter = viewAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
menuDrawer.toggleMenu()
|
||||
return true
|
||||
}
|
||||
R.id.main_shuffle -> {
|
||||
val intent = Intent(this, DownloadActivity::class.java)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true)
|
||||
startActivityForResultWithoutTransition(this, intent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getMusicFolderName(musicFolders: List<MusicFolder>): String {
|
||||
val musicFolderId = activeServerProvider.getActiveServer().musicFolderId
|
||||
if (musicFolderId != null && musicFolderId != "") {
|
||||
for ((id, name) in musicFolders) {
|
||||
if (id == musicFolderId) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
return getText(R.string.select_artist_all_folders).toString()
|
||||
}
|
||||
|
||||
private fun onItemClick(artist: Artist) {
|
||||
val intent = Intent(this, SelectAlbumActivity::class.java)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, artist.id)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, artist.name)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID, artist.id)
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ARTIST, true)
|
||||
startActivityForResultWithoutTransition(this, intent)
|
||||
}
|
||||
|
||||
private fun onFolderClick(view: View) {
|
||||
val popup = PopupMenu(this, view)
|
||||
|
||||
val musicFolderId = activeServerProvider.getActiveServer().musicFolderId
|
||||
var menuItem = popup.menu.add(
|
||||
MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders
|
||||
)
|
||||
if (musicFolderId == null || musicFolderId.isEmpty()) {
|
||||
menuItem.isChecked = true
|
||||
}
|
||||
if (musicFolders != null) {
|
||||
for (i in musicFolders!!.indices) {
|
||||
val (id, name) = musicFolders!![i]
|
||||
menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name)
|
||||
if (id == musicFolderId) {
|
||||
menuItem.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true)
|
||||
|
||||
popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) }
|
||||
popup.show()
|
||||
}
|
||||
|
||||
private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.artist_menu_play_now ->
|
||||
downloadRecursively(artist.id, false, false, true, false, false, false, false, true)
|
||||
R.id.artist_menu_play_next ->
|
||||
downloadRecursively(artist.id, false, false, true, true, false, true, false, true)
|
||||
R.id.artist_menu_play_last ->
|
||||
downloadRecursively(artist.id, false, true, false, false, false, false, false, true)
|
||||
R.id.artist_menu_pin ->
|
||||
downloadRecursively(artist.id, true, true, false, false, false, false, false, true)
|
||||
R.id.artist_menu_unpin ->
|
||||
downloadRecursively(artist.id, false, false, false, false, false, false, true, true)
|
||||
R.id.artist_menu_download ->
|
||||
downloadRecursively(artist.id, false, false, false, false, true, false, false, true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
val selectedFolder = if (menuItem.itemId == -1) null else musicFolders!![menuItem.itemId]
|
||||
val musicFolderId = selectedFolder?.id
|
||||
val musicFolderName = selectedFolder?.name
|
||||
?: getString(R.string.select_artist_all_folders)
|
||||
if (!isOffline(this)) {
|
||||
val currentSetting = activeServerProvider.getActiveServer()
|
||||
currentSetting.musicFolderId = musicFolderId
|
||||
serverSettingsModel.updateItem(currentSetting)
|
||||
}
|
||||
viewAdapter.setFolderName(musicFolderName)
|
||||
artistListModel.refresh(refreshArtistListView!!)
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MENU_GROUP_MUSIC_FOLDER = 10
|
||||
}
|
||||
}
|
@ -6,17 +6,22 @@ import org.koin.core.context.startKoin
|
||||
import org.koin.core.logger.Level
|
||||
import org.moire.ultrasonic.BuildConfig
|
||||
import org.moire.ultrasonic.di.appPermanentStorage
|
||||
import org.moire.ultrasonic.di.applicationModule
|
||||
import org.moire.ultrasonic.di.baseNetworkModule
|
||||
import org.moire.ultrasonic.di.directoriesModule
|
||||
import org.moire.ultrasonic.di.featureFlagsModule
|
||||
import org.moire.ultrasonic.di.mediaPlayerModule
|
||||
import org.moire.ultrasonic.di.musicServiceModule
|
||||
import org.moire.ultrasonic.log.FileLoggerTree
|
||||
import org.moire.ultrasonic.log.timberLogger
|
||||
import org.moire.ultrasonic.log.TimberKoinLogger
|
||||
import org.moire.ultrasonic.util.Util
|
||||
import timber.log.Timber
|
||||
import timber.log.Timber.DebugTree
|
||||
|
||||
/**
|
||||
* The Main class of the Application
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class UApp : MultiDexApplication() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@ -29,12 +34,12 @@ class UApp : MultiDexApplication() {
|
||||
}
|
||||
|
||||
startKoin {
|
||||
// TODO Current version of Koin has a bug, which forces the usage of Level.ERROR
|
||||
timberLogger(Level.ERROR)
|
||||
logger(TimberKoinLogger(Level.INFO))
|
||||
// declare Android context
|
||||
androidContext(this@UApp)
|
||||
// declare modules to use
|
||||
modules(
|
||||
applicationModule,
|
||||
directoriesModule,
|
||||
appPermanentStorage,
|
||||
baseNetworkModule,
|
||||
|
@ -5,14 +5,16 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.viewmodel.dsl.viewModel
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
import org.moire.ultrasonic.activity.ServerSettingsModel
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.data.AppDatabase
|
||||
import org.moire.ultrasonic.data.MIGRATION_1_2
|
||||
import org.moire.ultrasonic.fragment.ServerSettingsModel
|
||||
import org.moire.ultrasonic.util.Util
|
||||
|
||||
const val SP_NAME = "Default_SP"
|
||||
|
||||
/**
|
||||
* This Koin module contains registration of classes related to permanent storage
|
||||
*/
|
||||
val appPermanentStorage = module {
|
||||
single(named(SP_NAME)) { Util.getPreferences(androidContext()) }
|
||||
|
||||
@ -30,6 +32,4 @@ val appPermanentStorage = module {
|
||||
single { get<AppDatabase>().serverSettingDao() }
|
||||
|
||||
viewModel { ServerSettingsModel(get(), get(), androidContext()) }
|
||||
|
||||
single { ActiveServerProvider(get(), androidContext()) }
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
package org.moire.ultrasonic.di
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||
import org.moire.ultrasonic.util.NowPlayingEventDistributor
|
||||
import org.moire.ultrasonic.util.PermissionUtil
|
||||
import org.moire.ultrasonic.util.ThemeChangedEventDistributor
|
||||
|
||||
/**
|
||||
* This Koin module contains the registration of general classes needed for Ultrasonic
|
||||
*/
|
||||
val applicationModule = module {
|
||||
single { ActiveServerProvider(get(), androidContext()) }
|
||||
single { ImageLoaderProvider(androidContext()) }
|
||||
single { PermissionUtil(androidContext()) }
|
||||
single { NowPlayingEventDistributor() }
|
||||
single { ThemeChangedEventDistributor() }
|
||||
}
|
@ -4,7 +4,7 @@ import okhttp3.OkHttpClient
|
||||
import org.koin.dsl.module
|
||||
|
||||
/**
|
||||
* Provides base network dependencies.
|
||||
* This Koin module provides base network dependencies.
|
||||
*/
|
||||
val baseNetworkModule = module {
|
||||
single { OkHttpClient.Builder().build() }
|
||||
|
@ -5,6 +5,9 @@ import org.koin.dsl.module
|
||||
import org.moire.ultrasonic.cache.AndroidDirectories
|
||||
import org.moire.ultrasonic.cache.Directories
|
||||
|
||||
/**
|
||||
* This Koin module contains the registration for Directories
|
||||
*/
|
||||
val directoriesModule = module {
|
||||
single { AndroidDirectories(get()) } bind Directories::class
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import org.moire.ultrasonic.featureflags.FeatureStorage
|
||||
|
||||
/**
|
||||
* This Koin module contains the registration for the Feature Flags
|
||||
*/
|
||||
val featureFlagsModule = module {
|
||||
factory { FeatureStorage(androidContext()) }
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user