Skip to content

Instantly share code, notes, and snippets.

@BadDaemon
Last active March 17, 2021 21:12
Show Gist options
  • Save BadDaemon/9060da3a83f6072ae799b6dbb0aed5b5 to your computer and use it in GitHub Desktop.
Save BadDaemon/9060da3a83f6072ae799b6dbb0aed5b5 to your computer and use it in GitHub Desktop.
diff --git a/src/org/lineageos/eleven/widgets/PlayPauseProgressButton.java b/src/org/lineageos/eleven/widgets/PlayPauseProgressButton.java
index 3821eef..9f4eb26 100644
--- a/src/org/lineageos/eleven/widgets/PlayPauseProgressButton.java
+++ b/src/org/lineageos/eleven/widgets/PlayPauseProgressButton.java
@@ -1,26 +1,26 @@
/*
- * Copyright (c) 2013, The Linux Foundation. All rights reserved.
- * Copyright (C) 2015 The CyanogenMod 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.
-
- * Note: This file was re-added only to provide a progress bar to the Bottom Action Bar, the main music player ui is unaffected by this file.
-
- */
+* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+* Copyright (C) 2015 The CyanogenMod 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 org.lineageos.eleven.widgets;
import android.content.Context;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
@@ -28,17 +28,34 @@ import org.lineageos.eleven.R;
import org.lineageos.eleven.utils.MusicUtils;
/**
- * This class handles the play-pause button as well as the circular progress bar
+ * This class handles the playpause button as well as the circular progress bar
* it self-updates the progress bar but the containing activity/fragment
* needs to add code to pause/resume this button to prevent unnecessary
* updates while the activity/fragment is not visible
*/
public class PlayPauseProgressButton extends FrameLayout {
+ private static String TAG = PlayPauseProgressButton.class.getSimpleName();
+ private static boolean DEBUG = false;
+ private static final int REVOLUTION_IN_DEGREES = 360;
+ private static final int HALF_REVOLUTION_IN_DEGREES = REVOLUTION_IN_DEGREES / 2;
+
private ProgressBar mProgressBar;
private PlayPauseButton mPlayPauseButton;
private Runnable mUpdateProgress;
private boolean mPaused;
+ private final int mSmallDistance;
+ private float mDragPercentage = 0.0f;
+ private boolean mDragEnabled = false;
+ private boolean mDragging = false;
+ private float mDownAngle;
+ private float mDragAngle;
+ private float mDownX;
+ private float mDownY;
+ private int mWidth;
+ private long mCurrentSongDuration;
+ private long mCurrentSongProgress;
+
public PlayPauseProgressButton(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -47,14 +64,16 @@ public class PlayPauseProgressButton extends FrameLayout {
// set paused to false since we shouldn't be typically created while not visible
mPaused = false;
+
+ mSmallDistance = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPlayPauseButton = (PlayPauseButton) findViewById(R.id.action_button_play);
- mProgressBar = (ProgressBar) findViewById(R.id.circularProgressBarAlt);
+ mPlayPauseButton = (PlayPauseButton)findViewById(R.id.action_button_play);
+ mProgressBar = (ProgressBar)findViewById(R.id.circularProgressBar);
}
@Override
@@ -70,8 +89,8 @@ public class PlayPauseProgressButton extends FrameLayout {
// rotate the progress bar 90 degrees counter clockwise so that the
// starting position is at the top
- mProgressBar.setPivotX(mProgressBar.getMeasuredWidth() / 2f);
- mProgressBar.setPivotY(mProgressBar.getMeasuredHeight() / 2f);
+ mProgressBar.setPivotX(mProgressBar.getMeasuredWidth() / 2);
+ mProgressBar.setPivotY(mProgressBar.getMeasuredHeight() / 2);
mProgressBar.setRotation(-90);
}
@@ -86,14 +105,45 @@ public class PlayPauseProgressButton extends FrameLayout {
setVisibility(VISIBLE);
}
+ /**
+ * Disables and sets the visibility to gone for the container
+ */
+ public void disableAndHide() {
+ // disable
+ setEnabled(false);
+
+ // hide our view
+ setVisibility(GONE);
+ }
+
+ /**
+ * Sets whether the user can drag the progress in a circular motion to seek the track
+ */
+ public void setDragEnabled(boolean enabled) {
+ mDragEnabled = enabled;
+ }
+
+ /**
+ * @return true if the user is actively dragging to seek
+ */
+ public boolean isDragging() {
+ return mDragEnabled && mDragging;
+ }
+
+ /**
+ * @return how far the user has dragged in the track in ms
+ */
+ public long getDragProgressInMs() {
+ return (long)(mDragPercentage * mCurrentSongDuration);
+ }
+
@Override
public void setEnabled(boolean enabled) {
// if the enabled state isn't changed, quit
- if (enabled == isEnabled()) {
- return;
- }
+ if (enabled == isEnabled()) return;
super.setEnabled(enabled);
+
// signal our state has changed
onStateChanged();
}
@@ -102,19 +152,36 @@ public class PlayPauseProgressButton extends FrameLayout {
* Pauses the progress bar periodic update logic
*/
public void pause() {
+ if (!mPaused) {
+ mPaused = true;
+
+ // signal our state has changed
+ onStateChanged();
+ }
+ }
+
+ /**
+ * Resumes the progress bar periodic update logic
+ */
+ public void resume() {
if (mPaused) {
- return;
+ mPaused = false;
+
+ // signal our state has changed
+ onStateChanged();
}
+ }
- mPaused = true;
- // signal our state has changed
- onStateChanged();
+ /**
+ * @return play pause button
+ */
+ public PlayPauseButton getPlayPauseButton() {
+ return mPlayPauseButton;
}
/**
* Signaled if the state has changed (either the enabled or paused flag)
- * When the state changes, we either kick off the updates or remove them
- * based on those flags
+ * When the state changes, we either kick off the updates or remove them based on those flags
*/
private void onStateChanged() {
// if we are enabled and not paused
@@ -133,13 +200,15 @@ public class PlayPauseProgressButton extends FrameLayout {
/**
* Updates the state of the progress bar and the play pause button
*/
- public void updateState() {
- long currentSongDuration = MusicUtils.duration();
- long currentSongProgress = MusicUtils.position();
+ private void updateState() {
+ mCurrentSongDuration = MusicUtils.duration();
+ mCurrentSongProgress = MusicUtils.position();
int progress = 0;
- if (currentSongDuration > 0) {
- progress = (int) (mProgressBar.getMax() * currentSongProgress / currentSongDuration);
+ if (isDragging()) {
+ progress = (int) (mDragPercentage * mProgressBar.getMax());
+ } else if (mCurrentSongDuration > 0) {
+ progress = (int) (mProgressBar.getMax() * mCurrentSongProgress / mCurrentSongDuration);
}
mProgressBar.setProgress(progress);
@@ -151,9 +220,13 @@ public class PlayPauseProgressButton extends FrameLayout {
*/
private void postUpdate() {
if (mUpdateProgress == null) {
- mUpdateProgress = () -> {
- updateState();
- postDelayed(mUpdateProgress, MusicUtils.UPDATE_FREQUENCY_MS);
+ mUpdateProgress = new Runnable() {
+ @Override
+ public void run() {
+ updateState();
+ postDelayed(mUpdateProgress, isDragging() ? MusicUtils.UPDATE_FREQUENCY_FAST_MS
+ : MusicUtils.UPDATE_FREQUENCY_MS);
+ }
};
}
@@ -172,4 +245,133 @@ public class PlayPauseProgressButton extends FrameLayout {
removeCallbacks(mUpdateProgress);
}
}
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldW, int oldH) {
+ mWidth = Math.min(w, h);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!mDragEnabled) {
+ return false;
+ }
+
+ return onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (!mDragEnabled || mCurrentSongDuration <= 0) {
+ return false;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = event.getX();
+ mDownY = event.getY();
+ mDownAngle = angle(mDownX, mDownY);
+ mDragAngle = REVOLUTION_IN_DEGREES
+ * (mCurrentSongProgress / (float) mCurrentSongDuration);
+ mDragPercentage = mDragAngle / REVOLUTION_IN_DEGREES;
+ mDragging = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // if the user has moved a certain distance
+ if (Math.sqrt(Math.pow(event.getX() - mDownX, 2)
+ + Math.pow(event.getY() - mDownY, 2)) < mSmallDistance) {
+ return false;
+ }
+
+ // if we weren't previously dragging, immediately kick off an update to reflect
+ // the change faster
+ if (!mDragging) {
+ postUpdate();
+ }
+
+ mDragging = true;
+ getParent().requestDisallowInterceptTouchEvent(true);
+
+ // calculate the amount of angle we've moved
+ final float deltaAngle = getDelta(x, y);
+ mDragAngle = cropAngle(mDragAngle + deltaAngle);
+ mDragPercentage = mDragAngle / REVOLUTION_IN_DEGREES;
+
+ if (DEBUG) {
+ Log.d(TAG, "Delta Angle: " + deltaAngle + ", Target Angle: " + mDownAngle);
+ }
+
+ return true;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ // if we were dragging, seek to where we dragged to
+ if (mDragging) {
+ MusicUtils.seek((long)(mDragPercentage * mCurrentSongDuration));
+ }
+ mDragging = false;
+ default:
+ break;
+ }
+ return mDragging;
+ }
+
+ /**
+ * Crops the angle between 0 and 360 - if the angle is < 0, it will return 0, if it is more than
+ * 360 it will return 360
+ */
+ private static float cropAngle(float angle) {
+ return Math.min(REVOLUTION_IN_DEGREES, Math.max(0.0f, angle));
+ }
+
+ /**
+ * Wraps the angle between -180 and 180. This assumes that the passed in
+ * angle is >= -360 and <= 360
+ */
+ private static float wrapHalfRevolution(float angle) {
+ if (angle < -HALF_REVOLUTION_IN_DEGREES) {
+ return angle + REVOLUTION_IN_DEGREES;
+ } else if (angle > HALF_REVOLUTION_IN_DEGREES) {
+ return angle - REVOLUTION_IN_DEGREES;
+ }
+
+ return angle;
+ }
+
+ /**
+ * Gets the change in angle from the down angle and updates the down angle to the current angle
+ */
+ private float getDelta(float x, float y) {
+ float angle = angle(x, y);
+ float deltaAngle = wrapHalfRevolution(angle - mDownAngle);
+ mDownAngle = angle;
+ return deltaAngle;
+ }
+
+ /**
+ * Calculates the angle at the point passed in based on the center of the button
+ */
+ private float angle(float x, float y) {
+ float center = mWidth / 2.0f;
+ x -= center;
+ y -= center;
+
+ if (x == 0.0f) {
+ if (y > 0.0f) {
+ return 180.0f;
+ } else {
+ return 0.0f;
+ }
+ }
+
+ float angle = (float) (Math.atan(y / x) / Math.PI * 180.0);
+ if (x > 0.0f) {
+ angle += 90;
+ } else {
+ angle += 270;
+ }
+ return angle;
+ }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment