Fix up and repackage

This commit is contained in:
JFronny 2022-05-14 15:47:55 +02:00
parent 4df292bddf
commit cde7fd6565
No known key found for this signature in database
GPG key ID: E76429612C2929F4
510 changed files with 2660 additions and 3312 deletions

View file

@ -0,0 +1,17 @@
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import code.name.monkey.appthemehelper.ThemeStore
class AccentIcon @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
imageTintList = ColorStateList.valueOf(ThemeStore.accentColor(context))
}
}

View file

@ -0,0 +1,194 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import androidx.annotation.FontRes;
import com.google.android.material.textview.MaterialTextView;
import io.github.muntashirakon.music.R;
public class BaselineGridTextView extends MaterialTextView {
private final float FOUR_DIP;
private int extraBottomPadding = 0;
private int extraTopPadding = 0;
private @FontRes int fontResId = 0;
private float lineHeightHint = 0f;
private float lineHeightMultiplierHint = 1f;
private boolean maxLinesByHeight = false;
public BaselineGridTextView(Context context) {
this(context, null);
}
public BaselineGridTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public BaselineGridTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.BaselineGridTextView, defStyleAttr, 0);
// first check TextAppearance for line height & font attributes
if (a.hasValue(R.styleable.BaselineGridTextView_android_textAppearance)) {
int textAppearanceId =
a.getResourceId(
R.styleable.BaselineGridTextView_android_textAppearance,
android.R.style.TextAppearance);
TypedArray ta =
context.obtainStyledAttributes(textAppearanceId, R.styleable.BaselineGridTextView);
parseTextAttrs(ta);
ta.recycle();
}
// then check view attrs
parseTextAttrs(a);
maxLinesByHeight = a.getBoolean(R.styleable.BaselineGridTextView_maxLinesByHeight, false);
a.recycle();
FOUR_DIP =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics());
computeLineHeight();
}
@Override
public int getCompoundPaddingBottom() {
// include extra padding to make the height a multiple of 4dp
return super.getCompoundPaddingBottom() + extraBottomPadding;
}
@Override
public int getCompoundPaddingTop() {
// include extra padding to place the first line's baseline on the grid
return super.getCompoundPaddingTop() + extraTopPadding;
}
public @FontRes int getFontResId() {
return fontResId;
}
public float getLineHeightHint() {
return lineHeightHint;
}
public void setLineHeightHint(float lineHeightHint) {
this.lineHeightHint = lineHeightHint;
computeLineHeight();
}
public float getLineHeightMultiplierHint() {
return lineHeightMultiplierHint;
}
public void setLineHeightMultiplierHint(float lineHeightMultiplierHint) {
this.lineHeightMultiplierHint = lineHeightMultiplierHint;
computeLineHeight();
}
public boolean getMaxLinesByHeight() {
return maxLinesByHeight;
}
public void setMaxLinesByHeight(boolean maxLinesByHeight) {
this.maxLinesByHeight = maxLinesByHeight;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
extraTopPadding = 0;
extraBottomPadding = 0;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
height += ensureBaselineOnGrid();
height += ensureHeightGridAligned(height);
setMeasuredDimension(getMeasuredWidth(), height);
checkMaxLines(height, MeasureSpec.getMode(heightMeasureSpec));
}
/**
* When measured with an exact height, text can be vertically clipped mid-line. Prevent this by
* setting the {@code maxLines} property based on the available space.
*/
private void checkMaxLines(int height, int heightMode) {
if (!maxLinesByHeight || heightMode != MeasureSpec.EXACTLY) {
return;
}
int textHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
int completeLines = (int) Math.floor(textHeight / getLineHeight());
setMaxLines(completeLines);
}
/** Ensures line height is a multiple of 4dp. */
private void computeLineHeight() {
final Paint.FontMetrics fm = getPaint().getFontMetrics();
final float fontHeight = Math.abs(fm.ascent - fm.descent) + fm.leading;
final float desiredLineHeight =
(lineHeightHint > 0) ? lineHeightHint : lineHeightMultiplierHint * fontHeight;
final int baselineAlignedLineHeight =
(int) ((FOUR_DIP * (float) Math.ceil(desiredLineHeight / FOUR_DIP)) + 0.5f);
setLineSpacing(baselineAlignedLineHeight - fontHeight, 1f);
}
/** Ensure that the first line of text sits on the 4dp grid. */
private int ensureBaselineOnGrid() {
float baseline = getBaseline();
float gridAlign = baseline % FOUR_DIP;
if (gridAlign != 0) {
extraTopPadding = (int) (FOUR_DIP - Math.ceil(gridAlign));
}
return extraTopPadding;
}
/** Ensure that height is a multiple of 4dp. */
private int ensureHeightGridAligned(int height) {
float gridOverhang = height % FOUR_DIP;
if (gridOverhang != 0) {
extraBottomPadding = (int) (FOUR_DIP - Math.ceil(gridOverhang));
}
return extraBottomPadding;
}
private void parseTextAttrs(TypedArray a) {
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightMultiplierHint)) {
lineHeightMultiplierHint =
a.getFloat(R.styleable.BaselineGridTextView_lineHeightMultiplierHint, 1f);
}
if (a.hasValue(R.styleable.BaselineGridTextView_lineHeightHint)) {
lineHeightHint = a.getDimensionPixelSize(R.styleable.BaselineGridTextView_lineHeightHint, 0);
}
if (a.hasValue(R.styleable.BaselineGridTextView_android_fontFamily)) {
fontResId = a.getResourceId(R.styleable.BaselineGridTextView_android_fontFamily, 0);
}
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.NavigationViewUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import dev.chrisbanes.insetter.applyInsetter
class BottomNavigationBarTinted @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BottomNavigationView(context, attrs, defStyleAttr) {
init {
// If we are in Immersive mode we have to just set empty OnApplyWindowInsetsListener as
// bottom, start, and end padding is always applied (with the help of OnApplyWindowInsetsListener) to
// BottomNavigationView to dodge the system navigation bar (so we basically clear that listener).
if (PreferenceUtil.isFullScreenMode) {
setOnApplyWindowInsetsListener { _, insets ->
insets
}
} else {
applyInsetter {
type(navigationBars = true) {
padding(vertical = true)
margin(horizontal = true)
}
}
}
labelVisibilityMode = PreferenceUtil.tabTitleMode
if (!PreferenceUtil.materialYou) {
val iconColor = ATHUtil.resolveColor(context, android.R.attr.colorControlNormal)
val accentColor = ThemeStore.accentColor(context)
NavigationViewUtil.setItemIconColors(
this,
ColorUtil.withAlpha(iconColor, 0.5f),
accentColor
)
NavigationViewUtil.setItemTextColors(
this,
ColorUtil.withAlpha(iconColor, 0.5f),
accentColor
)
itemRippleColor = ColorStateList.valueOf(accentColor.addAlpha(0.08F))
itemActiveIndicatorColor = ColorStateList.valueOf(accentColor.addAlpha(0.12F))
}
}
}
fun Int.addAlpha(alpha: Float): Int {
return ColorUtil.withAlpha(this, alpha)
}

View file

@ -0,0 +1,458 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views;
import android.content.Context;
import android.graphics.PorterDuff;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import code.name.monkey.appthemehelper.util.ATHUtil;
import io.github.muntashirakon.music.R;
/** @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) */
public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener {
@ColorInt private int contentColorActivated;
@ColorInt private int contentColorDeactivated;
private int mActive;
private SelectionCallback mCallback;
private LinearLayout mChildFrame;
// Stores currently visible crumbs
private List<Crumb> mCrumbs;
// Stores user's navigation history, like a fragment back stack
private List<Crumb> mHistory;
// Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards
private List<Crumb> mOldCrumbs;
public BreadCrumbLayout(Context context) {
super(context);
init();
}
public BreadCrumbLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) {
LinearLayout view =
(LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false);
view.setTag(mCrumbs.size());
view.setOnClickListener(this);
ImageView iv = (ImageView) view.getChildAt(1);
if (iv.getDrawable() != null) {
iv.getDrawable().setAutoMirrored(true);
}
iv.setVisibility(View.GONE);
mChildFrame.addView(
view,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mCrumbs.add(crumb);
if (refreshLayout) {
mActive = mCrumbs.size() - 1;
requestLayout();
}
invalidateActivatedAll();
}
public void addHistory(Crumb crumb) {
mHistory.add(crumb);
}
public void clearCrumbs() {
try {
mOldCrumbs = new ArrayList<>(mCrumbs);
mCrumbs.clear();
mChildFrame.removeAllViews();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public void clearHistory() {
mHistory.clear();
}
public Crumb findCrumb(@NonNull File forDir) {
for (int i = 0; i < mCrumbs.size(); i++) {
if (mCrumbs.get(i).getFile().equals(forDir)) {
return mCrumbs.get(i);
}
}
return null;
}
public int getActiveIndex() {
return mActive;
}
public Crumb getCrumb(int index) {
return mCrumbs.get(index);
}
public SavedStateWrapper getStateWrapper() {
return new SavedStateWrapper(this);
}
public int historySize() {
return mHistory.size();
}
public Crumb lastHistory() {
if (mHistory.size() == 0) {
return null;
}
return mHistory.get(mHistory.size() - 1);
}
@Override
public void onClick(View v) {
if (mCallback != null) {
int index = (Integer) v.getTag();
mCallback.onCrumbSelection(mCrumbs.get(index), index);
}
}
public boolean popHistory() {
if (mHistory.size() == 0) {
return false;
}
mHistory.remove(mHistory.size() - 1);
return mHistory.size() != 0;
}
public void restoreFromStateWrapper(SavedStateWrapper mSavedState) {
if (mSavedState != null) {
mActive = mSavedState.mActive;
for (Crumb c : mSavedState.mCrumbs) {
addCrumb(c, false);
}
requestLayout();
setVisibility(mSavedState.mVisibility);
}
}
public void reverseHistory() {
Collections.reverse(mHistory);
}
public void setActivatedContentColor(@ColorInt int contentColorActivated) {
this.contentColorActivated = contentColorActivated;
}
public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) {
if (forceRecreate || !setActive(crumb)) {
clearCrumbs();
final List<File> newPathSet = new ArrayList<>();
newPathSet.add(0, crumb.getFile());
File p = crumb.getFile();
while ((p = p.getParentFile()) != null) {
newPathSet.add(0, p);
}
for (int index = 0; index < newPathSet.size(); index++) {
final File fi = newPathSet.get(index);
crumb = new Crumb(fi);
// Restore scroll positions saved before clearing
if (mOldCrumbs != null) {
for (Iterator<Crumb> iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) {
Crumb old = iterator.next();
if (old.equals(crumb)) {
crumb.setScrollPosition(old.getScrollPosition());
iterator.remove(); // minimize number of linear passes by removing un-used crumbs from
// history
break;
}
}
}
addCrumb(crumb, true);
}
// History no longer needed
mOldCrumbs = null;
}
}
public void setCallback(SelectionCallback callback) {
mCallback = callback;
}
public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) {
this.contentColorDeactivated = contentColorDeactivated;
}
public int size() {
return mCrumbs.size();
}
public boolean trim(String path, boolean dir) {
if (!dir) {
return false;
}
int index = -1;
for (int i = mCrumbs.size() - 1; i >= 0; i--) {
File fi = mCrumbs.get(i).getFile();
if (fi.getPath().equals(path)) {
index = i;
break;
}
}
boolean removedActive = index >= mActive;
if (index > -1) {
while (index <= mCrumbs.size() - 1) {
removeCrumbAt(index);
}
if (mChildFrame.getChildCount() > 0) {
int lastIndex = mCrumbs.size() - 1;
invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false);
}
}
return removedActive || mCrumbs.size() == 0;
}
public boolean trim(File file) {
return trim(file.getPath(), file.isDirectory());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// RTL works fine like this
View child = mChildFrame.getChildAt(mActive);
if (child != null) {
smoothScrollTo(child.getLeft(), 0);
}
}
void invalidateActivatedAll() {
for (int i = 0; i < mCrumbs.size(); i++) {
Crumb crumb = mCrumbs.get(i);
invalidateActivated(
mChildFrame.getChildAt(i),
mActive == mCrumbs.indexOf(crumb),
false,
i < mCrumbs.size() - 1)
.setText(crumb.getTitle());
}
}
void removeCrumbAt(int index) {
mCrumbs.remove(index);
mChildFrame.removeViewAt(index);
}
void updateIndices() {
for (int i = 0; i < mChildFrame.getChildCount(); i++) {
mChildFrame.getChildAt(i).setTag(i);
}
}
private void init() {
contentColorActivated =
ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorPrimary);
contentColorDeactivated =
ATHUtil.INSTANCE.resolveColor(getContext(), android.R.attr.textColorSecondary);
setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height));
setClipToPadding(false);
setHorizontalScrollBarEnabled(false);
mCrumbs = new ArrayList<>();
mHistory = new ArrayList<>();
mChildFrame = new LinearLayout(getContext());
addView(
mChildFrame,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
private TextView invalidateActivated(
View view,
final boolean isActive,
final boolean noArrowIfAlone,
final boolean allowArrowVisible) {
int contentColor = isActive ? contentColorActivated : contentColorDeactivated;
LinearLayout child = (LinearLayout) view;
TextView tv = (TextView) child.getChildAt(0);
tv.setTextColor(contentColor);
ImageView iv = (ImageView) child.getChildAt(1);
iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN);
if (noArrowIfAlone && getChildCount() == 1) {
iv.setVisibility(View.GONE);
} else if (allowArrowVisible) {
iv.setVisibility(View.VISIBLE);
} else {
iv.setVisibility(View.GONE);
}
return tv;
}
private boolean setActive(Crumb newActive) {
mActive = mCrumbs.indexOf(newActive);
invalidateActivatedAll();
boolean success = mActive > -1;
if (success) {
requestLayout();
}
return success;
}
public interface SelectionCallback {
void onCrumbSelection(Crumb crumb, int index);
}
public static class Crumb implements Parcelable {
public static final Creator<Crumb> CREATOR =
new Creator<Crumb>() {
@Override
public Crumb createFromParcel(Parcel source) {
return new Crumb(source);
}
@Override
public Crumb[] newArray(int size) {
return new Crumb[size];
}
};
private final File file;
private int scrollPos;
public Crumb(File file) {
this.file = file;
}
protected Crumb(Parcel in) {
this.file = (File) in.readSerializable();
this.scrollPos = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
return (o instanceof Crumb)
&& ((Crumb) o).getFile() != null
&& ((Crumb) o).getFile().equals(getFile());
}
public File getFile() {
return file;
}
public int getScrollPosition() {
return scrollPos;
}
public void setScrollPosition(int scrollY) {
this.scrollPos = scrollY;
}
public String getTitle() {
return file.getPath().equals("/") ? "root" : file.getName();
}
@NonNull
@Override
public String toString() {
return "Crumb{" + "file=" + file + ", scrollPos=" + scrollPos + '}';
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(this.file);
dest.writeInt(this.scrollPos);
}
}
public static class SavedStateWrapper implements Parcelable {
public static final Creator<SavedStateWrapper> CREATOR =
new Creator<SavedStateWrapper>() {
public SavedStateWrapper createFromParcel(Parcel source) {
return new SavedStateWrapper(source);
}
public SavedStateWrapper[] newArray(int size) {
return new SavedStateWrapper[size];
}
};
public final int mActive;
public final List<Crumb> mCrumbs;
public final int mVisibility;
public SavedStateWrapper(BreadCrumbLayout view) {
mActive = view.mActive;
mCrumbs = view.mCrumbs;
mVisibility = view.getVisibility();
}
protected SavedStateWrapper(Parcel in) {
this.mActive = in.readInt();
this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR);
this.mVisibility = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mActive);
dest.writeTypedList(mCrumbs);
dest.writeInt(this.mVisibility);
}
}
}

View file

@ -0,0 +1,337 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import androidx.appcompat.widget.AppCompatImageView;
import io.github.muntashirakon.music.R;
public class CircularImageView extends AppCompatImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
// Default Values
private static final float DEFAULT_BORDER_WIDTH = 4;
private static final float DEFAULT_SHADOW_RADIUS = 8.0f;
// Properties
private float borderWidth;
private int canvasSize;
private float shadowRadius;
private int shadowColor = Color.BLACK;
// Object used to draw
private Bitmap image;
private Drawable drawable;
private Paint paint;
private Paint paintBorder;
// region Constructor & Init Method
public CircularImageView(final Context context) {
this(context, null);
}
public CircularImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
// Init paint
paint = new Paint();
paint.setAntiAlias(true);
paintBorder = new Paint();
paintBorder.setAntiAlias(true);
// Load the styled attributes and set their properties
TypedArray attributes =
context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0);
// Init Border
if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) {
float defaultBorderSize =
DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density;
setBorderWidth(
attributes.getDimension(
R.styleable.CircularImageView_civ_border_width, defaultBorderSize));
setBorderColor(
attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE));
}
// Init Shadow
if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) {
shadowRadius = DEFAULT_SHADOW_RADIUS;
drawShadow(
attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius),
attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor));
}
attributes.recycle();
}
// endregion
// region Set Attr Method
public void setBorderWidth(float borderWidth) {
this.borderWidth = borderWidth;
requestLayout();
invalidate();
}
public void setBorderColor(int borderColor) {
if (paintBorder != null) {
paintBorder.setColor(borderColor);
}
invalidate();
}
public void addShadow() {
if (shadowRadius == 0) {
shadowRadius = DEFAULT_SHADOW_RADIUS;
}
drawShadow(shadowRadius, shadowColor);
invalidate();
}
public void setShadowRadius(float shadowRadius) {
drawShadow(shadowRadius, shadowColor);
invalidate();
}
public void setShadowColor(int shadowColor) {
drawShadow(shadowRadius, shadowColor);
invalidate();
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(
String.format(
"ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.",
scaleType));
}
}
// endregion
// region Draw Method
@Override
public void onDraw(Canvas canvas) {
// Load the bitmap
loadBitmap();
// Check if image isn't null
if (image == null) {
return;
}
if (!isInEditMode()) {
canvasSize = canvas.getWidth();
if (canvas.getHeight() < canvasSize) {
canvasSize = canvas.getHeight();
}
}
// circleCenter is the x or y of the view's center
// radius is the radius in pixels of the cirle to be drawn
// paint contains the shader that will texture the shape
int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2;
// Draw Border
canvas.drawCircle(
circleCenter + borderWidth,
circleCenter + borderWidth,
circleCenter + borderWidth - (shadowRadius + shadowRadius / 2),
paintBorder);
// Draw CircularImageView
canvas.drawCircle(
circleCenter + borderWidth,
circleCenter + borderWidth,
circleCenter - (shadowRadius + shadowRadius / 2),
paint);
}
private void loadBitmap() {
if (this.drawable == getDrawable()) {
return;
}
this.drawable = getDrawable();
this.image = drawableToBitmap(this.drawable);
updateShader();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasSize = w;
if (h < canvasSize) {
canvasSize = h;
}
if (image != null) {
updateShader();
}
}
private void drawShadow(float shadowRadius, int shadowColor) {
this.shadowRadius = shadowRadius;
this.shadowColor = shadowColor;
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder);
paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor);
}
private void updateShader() {
if (image == null) {
return;
}
// Crop Center Image
image = cropBitmap(image);
// Create Shader
BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// Center Image in Shader
Matrix matrix = new Matrix();
matrix.setScale(
(float) canvasSize / (float) image.getWidth(),
(float) canvasSize / (float) image.getHeight());
shader.setLocalMatrix(matrix);
// Set Shader in Paint
paint.setShader(shader);
}
private Bitmap cropBitmap(Bitmap bitmap) {
Bitmap bmp;
if (bitmap.getWidth() >= bitmap.getHeight()) {
bmp =
Bitmap.createBitmap(
bitmap,
bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
0,
bitmap.getHeight(),
bitmap.getHeight());
} else {
bmp =
Bitmap.createBitmap(
bitmap,
0,
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
bitmap.getWidth(),
bitmap.getWidth());
}
return bmp;
}
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable == null) {
return null;
} else if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
int intrinsicWidth = drawable.getIntrinsicWidth();
int intrinsicHeight = drawable.getIntrinsicHeight();
if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) {
return null;
}
try {
// Create Bitmap object out of the drawable
Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
// Simply return null of failed bitmap creations
Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!");
return null;
}
}
// endregion
// region Mesure Method
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
/*int imageSize = (width < height) ? width : height;
setMeasuredDimension(imageSize, imageSize);*/
setMeasuredDimension(width, height);
}
private int measureWidth(int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// The parent has determined an exact size for the child.
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// The parent has not imposed any constraint on the child.
result = canvasSize;
}
return result;
}
private int measureHeight(int measureSpecHeight) {
int result;
int specMode = MeasureSpec.getMode(measureSpecHeight);
int specSize = MeasureSpec.getSize(measureSpecHeight);
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// The child can be as large as it wants up to the specified size.
result = specSize;
} else {
// Measure the text (beware: ascent is a negative number)
result = canvasSize;
}
return (result + 2);
}
// endregion
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.RetroColorUtil
import com.google.android.material.color.MaterialColors
class ColorIconsImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
// Load the styled attributes and set their properties
context.withStyledAttributes(attrs, R.styleable.ColorIconsImageView, 0, 0) {
val color = getColor(R.styleable.ColorIconsImageView_iconBackgroundColor, Color.RED)
setIconBackgroundColor(color)
}
}
fun setIconBackgroundColor(color: Int) {
background = ContextCompat.getDrawable(context, R.drawable.color_circle_gradient)
if (ATHUtil.isWindowBackgroundDark(context) && PreferenceUtil.isDesaturatedColor) {
val desaturatedColor = RetroColorUtil.desaturateColor(color, 0.4f)
backgroundTintList = ColorStateList.valueOf(desaturatedColor)
imageTintList =
ColorStateList.valueOf(ATHUtil.resolveColor(context, R.attr.colorSurface))
} else {
val finalColor = MaterialColors.harmonize(
color,
ThemeStore.accentColor(context)
)
backgroundTintList = ColorStateList.valueOf(ColorUtil.adjustAlpha(finalColor, 0.22f))
imageTintList = ColorStateList.valueOf(ColorUtil.withAlpha(finalColor, 0.75f))
}
requestLayout()
invalidate()
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views;
import android.graphics.drawable.GradientDrawable;
public class DrawableGradient extends GradientDrawable {
public DrawableGradient(Orientation orientations, int[] colors, int shape) {
super(orientations, colors);
try {
setShape(shape);
setGradientType(GradientDrawable.LINEAR_GRADIENT);
setCornerRadius(0);
} catch (Exception e) {
e.printStackTrace();
}
}
public DrawableGradient SetTransparency(int transparencyPercent) {
this.setAlpha(255 - ((255 * transparencyPercent) / 100));
return this;
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class HeightFitSquareLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : FrameLayout(context, attrs, defStyleAttr){
private var forceSquare = true
fun setForceSquare(forceSquare: Boolean) {
this.forceSquare = forceSquare
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var i = widthMeasureSpec
if (forceSquare) {
i = heightMeasureSpec
}
super.onMeasure(i, heightMeasureSpec)
}
}

View file

@ -0,0 +1,50 @@
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import io.github.muntashirakon.music.databinding.BannerImageLayoutBinding
import io.github.muntashirakon.music.databinding.UserImageLayoutBinding
import io.github.muntashirakon.music.util.PreferenceUtil
class HomeImageLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1,
defStyleRes: Int = -1
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private var userImageBinding: UserImageLayoutBinding? = null
private var bannerImageBinding: BannerImageLayoutBinding? = null
init {
if (PreferenceUtil.isHomeBanner) {
bannerImageBinding = BannerImageLayoutBinding.inflate(LayoutInflater.from(context), this, true)
} else {
userImageBinding = UserImageLayoutBinding.inflate(LayoutInflater.from(context), this, true)
}
}
val userImage: ImageView
get() = if (PreferenceUtil.isHomeBanner) {
bannerImageBinding!!.userImage
} else {
userImageBinding!!.userImage
}
val bannerImage: ImageView?
get() = if (PreferenceUtil.isHomeBanner) {
bannerImageBinding!!.bannerImage
} else {
null
}
val titleWelcome : TextView
get() = if (PreferenceUtil.isHomeBanner) {
bannerImageBinding!!.titleWelcome
} else {
userImageBinding!!.titleWelcome
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.content.withStyledAttributes
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.databinding.ListItemViewNoCardBinding
import io.github.muntashirakon.music.extensions.hide
import io.github.muntashirakon.music.extensions.show
/**
* Created by hemanths on 2019-10-02.
*/
class ListItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : FrameLayout(context, attrs, defStyleAttr) {
private var binding =
ListItemViewNoCardBinding.inflate(LayoutInflater.from(context), this, true)
init {
context.withStyledAttributes(attrs, R.styleable.ListItemView) {
if (hasValue(R.styleable.ListItemView_listItemIcon)) {
binding.icon.setImageDrawable(getDrawable(R.styleable.ListItemView_listItemIcon))
} else {
binding.icon.hide()
}
binding.title.text = getText(R.styleable.ListItemView_listItemTitle)
if (hasValue(R.styleable.ListItemView_listItemSummary)) {
binding.summary.text = getText(R.styleable.ListItemView_listItemSummary)
} else {
binding.summary.hide()
}
}
}
fun setSummary(appVersion: String) {
binding.summary.show()
binding.summary.text = appVersion
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.webkit.WebView
class LollipopFixedWebView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : WebView(getFixedContext(context), attrs, defStyleAttr){
companion object {
fun getFixedContext(context: Context): Context {
return context.createConfigurationContext(Configuration())
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.withStyledAttributes
import io.github.muntashirakon.music.R
import com.bumptech.glide.Glide
/** @author Hemanth S (h4h13).
*/
class NetworkImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
CircularImageView(context, attrs, defStyleAttr) {
init {
context.withStyledAttributes(attrs, R.styleable.NetworkImageView, 0, 0) {
val url = getString(R.styleable.NetworkImageView_url_link)
setImageUrl(context, url!!)
}
}
fun setImageUrl(imageUrl: String) {
setImageUrl(context, imageUrl)
}
private fun setImageUrl(context: Context, imageUrl: String) {
Glide.with(context)
.load(imageUrl)
.error(R.drawable.ic_account)
.placeholder(R.drawable.ic_account)
.into(this)
}
}

View file

@ -0,0 +1,49 @@
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.content.withStyledAttributes
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.databinding.ItemPermissionBinding
import io.github.muntashirakon.music.extensions.accentOutlineColor
class PermissionItem @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1,
defStyleRes: Int = -1
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private var binding: ItemPermissionBinding
val checkImage get() = binding.checkImage
init {
binding = ItemPermissionBinding.inflate(LayoutInflater.from(context), this, true)
context.withStyledAttributes(attrs, R.styleable.PermissionItem, 0, 0) {
binding.title.text = getText(R.styleable.PermissionItem_permissionTitle)
binding.summary.text = getText(R.styleable.PermissionItem_permissionTitleSubTitle)
binding.number.text = getText(R.styleable.PermissionItem_permissionTitleNumber)
binding.button.text = getText(R.styleable.PermissionItem_permissionButtonTitle)
binding.button.setIconResource(
getResourceId(
R.styleable.PermissionItem_permissionIcon,
R.drawable.ic_album
)
)
val color = ThemeStore.accentColor(context)
binding.number.backgroundTintList =
ColorStateList.valueOf(ColorUtil.withAlpha(color, 0.22f))
if (!isInEditMode) binding.button.accentOutlineColor()
}
}
fun setButtonClick(callBack: () -> Unit) {
binding.button.setOnClickListener { callBack() }
}
}

View file

@ -0,0 +1,162 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
import io.github.muntashirakon.music.R;
public class PopupBackground extends Drawable {
private final int mPaddingEnd;
private final int mPaddingStart;
@NonNull private final Paint mPaint;
@NonNull private final Path mPath = new Path();
@NonNull private final Matrix mTempMatrix = new Matrix();
public PopupBackground(@NonNull Context context, int color) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(color);
mPaint.setStyle(Paint.Style.FILL);
Resources resources = context.getResources();
mPaddingStart = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_start);
mPaddingEnd = resources.getDimensionPixelOffset(R.dimen.afs_md2_popup_padding_end);
}
private static void pathArcTo(
@NonNull Path path,
float centerX,
float centerY,
float radius,
float startAngle,
float sweepAngle) {
path.arcTo(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius,
startAngle,
sweepAngle,
false);
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void getOutline(@NonNull Outline outline) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !mPath.isConvex()) {
// The outline path must be convex before Q, but we may run into floating point error
// caused by calculation involving sqrt(2) or OEM implementation difference, so in this
// case we just omit the shadow instead of crashing.
super.getOutline(outline);
return;
}
outline.setConvexPath(mPath);
}
@Override
public void setAlpha(int alpha) {}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {}
@Override
public boolean getPadding(@NonNull Rect padding) {
if (needMirroring()) {
padding.set(mPaddingEnd, 0, mPaddingStart, 0);
} else {
padding.set(mPaddingStart, 0, mPaddingEnd, 0);
}
return true;
}
@Override
public boolean isAutoMirrored() {
return true;
}
@Override
public boolean onLayoutDirectionChanged(int layoutDirection) {
updatePath();
return true;
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
updatePath();
}
private boolean needMirroring() {
return DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL;
}
private void updatePath() {
mPath.reset();
Rect bounds = getBounds();
float width = bounds.width();
float height = bounds.height();
float r = height / 2;
float sqrt2 = (float) Math.sqrt(2);
// Ensure we are convex.
width = Math.max(r + sqrt2 * r, width);
pathArcTo(mPath, r, r, r, 90, 180);
float o1X = width - sqrt2 * r;
pathArcTo(mPath, o1X, r, r, -90, 45f);
float r2 = r / 5;
float o2X = width - sqrt2 * r2;
pathArcTo(mPath, o2X, r, r2, -45, 90);
pathArcTo(mPath, o1X, r, r, 45f, 45f);
mPath.close();
if (needMirroring()) {
mTempMatrix.setScale(-1, 1, width / 2, 0);
} else {
mTempMatrix.reset();
}
mTempMatrix.postTranslate(bounds.left, bounds.top);
mPath.transform(mTempMatrix);
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.withStyledAttributes
import io.github.muntashirakon.music.R
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
class RetroShapeableImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = -1
) : ShapeableImageView(context, attrs, defStyle) {
init {
context.withStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1) {
addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
val radius = width / 2f
shapeAppearanceModel = ShapeAppearanceModel().withCornerSize(radius)
}
}
}
private fun updateCornerSize(cornerSize: Float) {
shapeAppearanceModel = ShapeAppearanceModel.Builder()
.setAllCorners(CornerFamily.ROUNDED, cornerSize)
.build()
}
//For square
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views;
import android.graphics.Rect;
import android.view.View;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.zhanghai.android.fastscroll.FastScroller;
public class ScrollingViewOnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
@NonNull private final Rect mPadding = new Rect();
@Nullable private final FastScroller mFastScroller;
public ScrollingViewOnApplyWindowInsetsListener(
@Nullable View view, @Nullable FastScroller fastScroller) {
if (view != null) {
mPadding.set(
view.getPaddingLeft(),
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
mFastScroller = fastScroller;
}
public ScrollingViewOnApplyWindowInsetsListener() {
this(null, null);
}
@NonNull
@Override
public WindowInsets onApplyWindowInsets(@NonNull View view, @NonNull WindowInsets insets) {
view.setPadding(
mPadding.left + insets.getSystemWindowInsetLeft(),
mPadding.top,
mPadding.right + insets.getSystemWindowInsetRight(),
mPadding.bottom + insets.getSystemWindowInsetBottom());
if (mFastScroller != null) {
mFastScroller.setPadding(
insets.getSystemWindowInsetLeft(),
0,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}
return insets;
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.content.withStyledAttributes
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.databinding.ListSettingItemViewBinding
/**
* Created by hemanths on 2019-12-10.
*/
class SettingListItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1,
defStyleRes: Int = -1
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
init {
val binding: ListSettingItemViewBinding =
ListSettingItemViewBinding.inflate(LayoutInflater.from(context), this, true)
context.withStyledAttributes(attrs, R.styleable.SettingListItemView) {
if (hasValue(R.styleable.SettingListItemView_settingListItemIcon)) {
binding.icon.setImageDrawable(getDrawable(R.styleable.SettingListItemView_settingListItemIcon))
}
binding.icon.setIconBackgroundColor(
getColor(R.styleable.SettingListItemView_settingListItemIconColor, Color.WHITE)
)
binding.title.text = getText(R.styleable.SettingListItemView_settingListItemTitle)
binding.text.text = getText(R.styleable.SettingListItemView_settingListItemText)
}
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
import android.view.View
class StatusBarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : View(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec), getStatusBarHeight(
resources
)
)
}
companion object {
fun getStatusBarHeight(r: Resources): Int {
var result = 0
val resourceId = r.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = r.getDimensionPixelSize(resourceId)
}
return result
}
}
}

View file

@ -0,0 +1,80 @@
package io.github.muntashirakon.music.views
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.appcompat.widget.Toolbar
import androidx.core.view.updateLayoutParams
import io.github.muntashirakon.music.databinding.CollapsingAppbarLayoutBinding
import io.github.muntashirakon.music.databinding.SimpleAppbarLayoutBinding
import io.github.muntashirakon.music.util.PreferenceUtil
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
import com.google.android.material.shape.MaterialShapeDrawable
import dev.chrisbanes.insetter.applyInsetter
class TopAppBarLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1,
) : AppBarLayout(context, attrs, defStyleAttr) {
private var simpleAppbarBinding: SimpleAppbarLayoutBinding? = null
private var collapsingAppbarBinding: CollapsingAppbarLayoutBinding? = null
val mode: AppBarMode = PreferenceUtil.appBarMode
init {
if (mode == AppBarMode.COLLAPSING) {
collapsingAppbarBinding =
CollapsingAppbarLayoutBinding.inflate(LayoutInflater.from(context), this, true)
val isLandscape =
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (isLandscape) {
fitsSystemWindows = false
}
} else {
simpleAppbarBinding =
SimpleAppbarLayoutBinding.inflate(LayoutInflater.from(context), this, true)
simpleAppbarBinding?.root?.applyInsetter {
type(navigationBars = true) {
padding(horizontal = true)
}
}
statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
}
}
fun pinWhenScrolled() {
simpleAppbarBinding?.root?.updateLayoutParams<LayoutParams> {
scrollFlags = SCROLL_FLAG_NO_SCROLL
}
}
val toolbar: Toolbar
get() = if (mode == AppBarMode.COLLAPSING) {
collapsingAppbarBinding?.toolbar!!
} else {
simpleAppbarBinding?.toolbar!!
}
var title: String
get() = if (mode == AppBarMode.COLLAPSING) {
collapsingAppbarBinding?.collapsingToolbarLayout?.title.toString()
} else {
simpleAppbarBinding?.appNameText?.text.toString()
}
set(value) {
if (mode == AppBarMode.COLLAPSING) {
collapsingAppbarBinding?.collapsingToolbarLayout?.title = value
} else {
simpleAppbarBinding?.appNameText?.text = value
}
}
enum class AppBarMode {
COLLAPSING,
SIMPLE
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.widget.AppCompatTextView
class VerticalTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(
context, attrs
) {
private var topDown = false
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec)
setMeasuredDimension(measuredHeight, measuredWidth)
}
override fun onDraw(canvas: Canvas) {
val textPaint = paint
textPaint.color = currentTextColor
textPaint.drawableState = drawableState
canvas.save()
if (topDown) {
canvas.translate(width.toFloat(), 0f)
canvas.rotate(90f)
} else {
canvas.translate(0f, height.toFloat())
canvas.rotate(-90f)
}
canvas.translate(compoundPaddingLeft.toFloat(), extendedPaddingTop.toFloat())
layout.draw(canvas)
canvas.restore()
}
init {
val gravity = gravity
topDown = if (Gravity.isVertical(gravity)
&& gravity and Gravity.VERTICAL_GRAVITY_MASK == Gravity.BOTTOM
) {
setGravity(gravity and Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.TOP)
false
} else true
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.card.MaterialCardView
/**
* Created by hemanths on 3/18/19
*/
class WidthFitSquareCardView : MaterialCardView {
constructor(context: Context) : super(context)
constructor(
context: Context,
attrs: AttributeSet
) : super(context, attrs)
constructor(
context: Context, attrs:
AttributeSet, defStyleAttr: Int
) : super(context, attrs, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This 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.
*
* This software 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.
*/
package io.github.muntashirakon.music.views
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
class WidthFitSquareLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : FrameLayout(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
}
}

View file

@ -0,0 +1,18 @@
package io.github.muntashirakon.music.views.insets
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import io.github.muntashirakon.music.extensions.drawAboveSystemBarsWithPadding
import io.github.muntashirakon.music.util.RetroUtil
class InsetsConstraintLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
init {
if (!RetroUtil.isLandscape)
drawAboveSystemBarsWithPadding()
}
}

View file

@ -0,0 +1,18 @@
package io.github.muntashirakon.music.views.insets
import android.content.Context
import android.util.AttributeSet
import android.widget.LinearLayout
import io.github.muntashirakon.music.extensions.drawAboveSystemBarsWithPadding
import io.github.muntashirakon.music.util.RetroUtil
class InsetsLinearLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
init {
if (!RetroUtil.isLandscape)
drawAboveSystemBarsWithPadding()
}
}

View file

@ -0,0 +1,27 @@
package io.github.muntashirakon.music.views.insets
import android.content.Context
import android.util.AttributeSet
import androidx.annotation.Px
import androidx.recyclerview.widget.RecyclerView
import io.github.muntashirakon.music.extensions.applyBottomInsets
class InsetsRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
init {
applyBottomInsets()
}
fun updatePadding(
@Px left: Int = paddingLeft,
@Px top: Int = paddingTop,
@Px right: Int = paddingRight,
@Px bottom: Int = paddingBottom
) {
setPadding(left, top, right, bottom)
applyBottomInsets()
}
}