Fix up and repackage
This commit is contained in:
parent
4df292bddf
commit
cde7fd6565
510 changed files with 2660 additions and 3312 deletions
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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() }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue