Skip to content

Commit

Permalink
Feat[launcher]: Add a quick settings menu (#6303)
Browse files Browse the repository at this point in the history
  • Loading branch information
artdeell authored Nov 26, 2024
2 parents a5d02c1 + eb6b23c commit d5fd73c
Show file tree
Hide file tree
Showing 44 changed files with 2,319 additions and 1,513 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1720909595576
1732218529630
138 changes: 138 additions & 0 deletions app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.kdt;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.SeekBar;

/**
* Seekbar with ability to handle ranges and increments
*/
@SuppressLint("AppCompatCustomView")
public class CustomSeekbar extends SeekBar {
private int mMin = 0;
private int mIncrement = 1;
private SeekBar.OnSeekBarChangeListener mListener;

/** When using increments, this flag is used to prevent double calls to the listener */
private boolean mInternalChanges = false;

public CustomSeekbar(Context context) {
super(context);
setup();
}

public CustomSeekbar(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}

public CustomSeekbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}

public CustomSeekbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setup();
}

public void setIncrement(int increment) {
mIncrement = increment;
}

public void setRange(int min, int max) {
mMin = min;
setMax(max - min);
}

@Override
public synchronized void setProgress(int progress) {
super.setProgress(applyIncrement(progress - mMin));
}

@Override
public void setProgress(int progress, boolean animate) {
super.setProgress(applyIncrement(progress - mMin), animate);
}

@Override
public synchronized int getProgress() {
return applyIncrement(super.getProgress() + mMin);
}

@Override
public synchronized void setMin(int min) {
super.setMin(min);
mMin = min;
}

/**
* Wrapper to allow for a listener to be set around the internal listener
*/
@Override
public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
mListener = l;
}

public void setup() {
super.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
/** Store the previous progress to prevent double calls with increments */
private int previousProgress = 0;
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mInternalChanges) return;
mInternalChanges = true;

progress += mMin;
progress = applyIncrement(progress);

if (progress != previousProgress) {
if (mListener != null) {
previousProgress = progress;
mListener.onProgressChanged(seekBar, progress, fromUser);
}
}

// Forces the thumb to snap to the increment
setProgress(progress);
mInternalChanges = false;
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (mInternalChanges) return;

if (mListener != null) {
mListener.onStartTrackingTouch(seekBar);
}
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (mInternalChanges) return;
mInternalChanges = true;

setProgress(seekBar.getProgress());

if (mListener != null) {
mListener.onStopTrackingTouch(seekBar);
}
mInternalChanges = false;
}
});
}

/**
* Apply increment to the progress
* @param progress Progress to apply increment to
* @return Progress with increment applied
*/
private int applyIncrement(int progress) {
if (mIncrement < 1) return progress;

progress = progress / mIncrement;
progress = progress * mIncrement;
return progress;
}
}
246 changes: 246 additions & 0 deletions app_pojavlauncher/src/main/java/com/kdt/SideDialogView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package com.kdt;

import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.CallSuper;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.res.ResourcesCompat;

import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;

/**
* The base class for side dialog views
* A side dialog is a dialog appearing from one side of the screen
*/
public abstract class SideDialogView {

private final ViewGroup mParent;
private final @LayoutRes int mLayoutId;
private ViewGroup mDialogLayout;
private DefocusableScrollView mScrollView;
protected View mDialogContent;

protected final int mMargin;
private ObjectAnimator mSideDialogAnimator;
protected boolean mDisplaying = false;
/* Whether the layout is built */
private boolean mIsInstantiated = false;

/* UI elements */
private Button mStartButton, mEndButton;
private TextView mTitleTextview;
private View mTitleDivider;

/* Data to store when the UI element has yet to be inflated */
private @StringRes int mStartButtonStringId, mEndButtonStringId, mTitleStringId;
private View.OnClickListener mStartButtonListener, mEndButtonListener;


public SideDialogView(Context context, ViewGroup parent, @LayoutRes int layoutId) {
mMargin = context.getResources().getDimensionPixelOffset(R.dimen._20sdp);
mParent = parent;
mLayoutId = layoutId;
}

public void setTitle(@StringRes int textId) {
mTitleStringId = textId;
if (mIsInstantiated) {
mTitleTextview.setText(textId);
mTitleTextview.setVisibility(View.VISIBLE);
mTitleDivider.setVisibility(View.VISIBLE);
}
}

public final void setStartButtonListener(@StringRes int textId, @Nullable View.OnClickListener listener) {
mStartButtonStringId = textId;
mStartButtonListener = listener;
if (mIsInstantiated) setButton(mStartButton, textId, listener);
}

public final void setEndButtonListener(@StringRes int textId, @Nullable View.OnClickListener listener) {
mEndButtonStringId = textId;
mEndButtonListener = listener;
if (mIsInstantiated) setButton(mEndButton, textId, listener);
}

private void setButton(@NonNull Button button, @StringRes int textId, @Nullable View.OnClickListener listener) {
button.setText(textId);
button.setOnClickListener(listener);
button.setVisibility(View.VISIBLE);
}


private void inflateLayout() {
if(mIsInstantiated) {
Log.w("SideDialogView", "Layout already inflated");
return;
}

// Inflate layouts
mDialogLayout = (ViewGroup) LayoutInflater.from(mParent.getContext()).inflate(R.layout.dialog_side_dialog, mParent, false);
mScrollView = mDialogLayout.findViewById(R.id.side_dialog_scrollview);
mStartButton = mDialogLayout.findViewById(R.id.side_dialog_start_button);
mEndButton = mDialogLayout.findViewById(R.id.side_dialog_end_button);
mTitleTextview = mDialogLayout.findViewById(R.id.side_dialog_title_textview);
mTitleDivider = mDialogLayout.findViewById(R.id.side_dialog_title_divider);

LayoutInflater.from(mParent.getContext()).inflate(mLayoutId, mScrollView, true);
mDialogContent = mScrollView.getChildAt(0);

// Attach layouts
mParent.addView(mDialogLayout);

mSideDialogAnimator = ObjectAnimator.ofFloat(mDialogLayout, "x", 0).setDuration(600);
mSideDialogAnimator.setInterpolator(new AccelerateDecelerateInterpolator());

mDialogLayout.setElevation(10);
mDialogLayout.setTranslationZ(10);

mDialogLayout.setVisibility(View.VISIBLE);
mDialogLayout.setBackground(ResourcesCompat.getDrawable(mDialogLayout.getResources(), R.drawable.background_control_editor, null));

//TODO offset better according to view width
mDialogLayout.setX(-mDialogLayout.getResources().getDimensionPixelOffset(R.dimen._280sdp));
mIsInstantiated = true;

// Set up UI elements
if (mTitleStringId != 0) setTitle(mTitleStringId);
if (mStartButtonStringId != 0) setStartButtonListener(mStartButtonStringId, mStartButtonListener);
if (mEndButtonStringId != 0) setEndButtonListener(mEndButtonStringId, mEndButtonListener);
}

/** Destroy the layout, cleanup variables */
private void deflateLayout() {
if(!mIsInstantiated) {
Log.w("SideDialogView", "Layout not inflated");
return;
}

mParent.removeView(mDialogLayout);
mIsInstantiated = false;

mScrollView = null;
mSideDialogAnimator = null;
mDialogLayout = null;
mDialogContent = null;
mTitleTextview = null;
mTitleDivider = null;
mStartButton = null;
mEndButton = null;
}


/**
* Slide the layout into the visible screen area
*/
@CallSuper
public final void appear(boolean fromRight) {
if (!mIsInstantiated) {
inflateLayout();
onInflate();
}

// To avoid UI sizing issue when the dialog is not fully inflated
onAppear();
Tools.runOnUiThread(() -> {
if (fromRight) {
if (!mDisplaying || !isAtRight()) {
mSideDialogAnimator.setFloatValues(currentDisplayMetrics.widthPixels, currentDisplayMetrics.widthPixels - mScrollView.getWidth() - mMargin);
mSideDialogAnimator.start();
mDisplaying = true;
}
} else {
if (!mDisplaying || isAtRight()) {
mSideDialogAnimator.setFloatValues(-mDialogLayout.getWidth(), mMargin);
mSideDialogAnimator.start();
mDisplaying = true;
}
}
});
}

protected final boolean isAtRight() {
return mDialogLayout.getX() > currentDisplayMetrics.widthPixels / 2f;
}

/**
* Slide out the layout
* @param destroy Whether the layout should be destroyed after disappearing.
* Recommended to be true if the layout is not going to be used anymore
*/
@CallSuper
public final void disappear(boolean destroy) {
if (!mDisplaying) {
if(destroy) {
onDisappear();
onDestroy();
deflateLayout();
}
return;
}

mDisplaying = false;
if (isAtRight())
mSideDialogAnimator.setFloatValues(currentDisplayMetrics.widthPixels - mDialogLayout.getWidth() - mMargin, currentDisplayMetrics.widthPixels);
else
mSideDialogAnimator.setFloatValues(mMargin, -mDialogLayout.getWidth());

if(destroy) {
onDisappear();
onDestroy();
mSideDialogAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mSideDialogAnimator.removeListener(this);
deflateLayout();
}
});
}

mSideDialogAnimator.start();
}

/** @return Whether the dialog is currently displaying */
public final boolean isDisplaying(){
return mDisplaying;
}

/**
* Called when the dialog is inflated, ideal for setting up UI elements bindings
*/
protected void onInflate() {}

/**
* Called after the dialog has appeared
*/
protected void onAppear() {}

/**
* Called after the dialog has disappeared
*/
protected void onDisappear() {}

/**
* Called before the dialog gets destroyed (removing views from parent)
* Ideal for cleaning up resources
*/
protected void onDestroy() {}


}
Loading

0 comments on commit d5fd73c

Please sign in to comment.