diff options
Diffstat (limited to 'src/org/cyanogenmod/theme/widget')
8 files changed, 1027 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java b/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java new file mode 100644 index 0000000..29534f7 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; + +public class AutoSnapHorizontalScrollView extends HorizontalScrollView { + private static final int SNAP_ON_UP_DELAY = 250; + + private int mScrollPositionOnUp; + + enum EventStates { + SCROLLING, + FLING + } + + private EventStates mSystemState = EventStates.SCROLLING; + + public AutoSnapHorizontalScrollView(Context context) { + super(context); + } + + public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + Runnable mSnapRunnable = new Runnable(){ + @Override + public void run() { + snapItems(); + mSystemState = EventStates.SCROLLING; + } + }; + + /** + * Added runnable for snapping on an item when the user lifts up their finger and + * there is no scrolling taking place (i.e. no flinging) + */ + Runnable mSnapOnUpRunnable = new Runnable(){ + @Override + public void run() { + int scrollX = getScrollX(); + if (scrollX != mScrollPositionOnUp) { + mScrollPositionOnUp = scrollX; + postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); + } else { + snapItems(); + mSystemState = EventStates.SCROLLING; + } + } + }; + + @Override + public boolean onTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mSystemState = EventStates.FLING; + removeCallbacks(mSnapRunnable); + mScrollPositionOnUp = getScrollX(); + postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); + } else if (action == MotionEvent.ACTION_DOWN) { + mSystemState = EventStates.SCROLLING; + removeCallbacks(mSnapRunnable); + removeCallbacks(mSnapOnUpRunnable); + } + return super.onTouchEvent(ev); + } + + private void snapItems() { + Rect parentBounds = new Rect(); + getDrawingRect(parentBounds); + Rect childBounds = new Rect(); + ViewGroup parent = (ViewGroup) getChildAt(0); + for (int i = 0; i < parent.getChildCount(); i++) { + View view = parent.getChildAt(i); + view.getHitRect(childBounds); + if (childBounds.right >= parentBounds.left && childBounds.left <= parentBounds.left) { + // First partially visible child + if ((childBounds.right - parentBounds.left) >= + (parentBounds.left - childBounds.left)) { + smoothScrollTo(Math.abs(childBounds.left), 0); + } else { + /** + * Added code to take into account dividers so that we do not see + * one on the edge of the screen when items snap in place. + */ + int dividerWidth = 0; + if (parent instanceof LinearLayout) { + dividerWidth = ((LinearLayout) parent).getDividerWidth(); + } + smoothScrollTo(Math.abs(childBounds.right) + dividerWidth, 0); + } + break; + } + } + } + + // Overwrite measureChildX as we want our child to be able to tell the + // parents width but do not impose any limits (AT_MOST; AT_MAX) + @Override + protected void measureChild(View child, int parentWidthMeasureSpec, + int parentHeightMeasureSpec) { + ViewGroup.LayoutParams lp = child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + + mPaddingBottom, lp.height); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) - (mPaddingLeft + mPaddingRight), + MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) - + (mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed), MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + if (mSystemState == EventStates.SCROLLING) { + return; + } + if (Math.abs(l - oldl) <= 1 && mSystemState == EventStates.FLING) { + removeCallbacks(mSnapRunnable); + postDelayed(mSnapRunnable, 100); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/BootAniImageView.java b/src/org/cyanogenmod/theme/widget/BootAniImageView.java new file mode 100644 index 0000000..b3f58a6 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/BootAniImageView.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.ImageView; + +import java.io.IOException; +import java.util.List; +import java.util.zip.ZipFile; + +import libcore.io.IoUtils; + +import org.cyanogenmod.theme.util.BootAnimationHelper; + +public class BootAniImageView extends ImageView { + private static final String TAG = BootAniImageView.class.getName(); + + private static final boolean DEBUG = false; + + private static final int MAX_BUFFERS = 2; + + private Bitmap[] mBuffers = new Bitmap[MAX_BUFFERS]; + private int mReadBufferIndex = 0; + private int mWriteBufferIndex = 0; + private ZipFile mBootAniZip; + + private List<BootAnimationHelper.AnimationPart> mAnimationParts; + private int mCurrentPart; + private int mCurrentFrame; + private int mCurrentPartPlayCount; + private int mFrameDuration; + + private boolean mActive = false; + + public BootAniImageView(Context context) { + this(context, null); + } + + public BootAniImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BootAniImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility == VISIBLE) { + if (mBootAniZip != null) start(); + } else { + stop(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + // In case we end up in the mid dle of onDraw while the buffers are being recycled + // we catch the exception and just let frame not be rendered. + try { + super.onDraw(canvas); + } catch (RuntimeException e) { + Log.e(TAG, "Unable to draw boot animation frame."); + } + } + + public synchronized boolean setBootAnimation(ZipFile bootAni) { + if (bootAni == null) { + Log.w(TAG, "Boot animation ZipFile is null."); + return false; + } + // make sure we are stopped first + stop(); + + if (mBootAniZip != null) { + IoUtils.closeQuietly(mBootAniZip); + // This boot animation may be a different size than the previous + // one so clear out the buffers so they can be recreated with + // the correct size. + for (int i = 0; i < MAX_BUFFERS; i++) { + if (mBuffers[i] != null) { + mBuffers[i].recycle(); + mBuffers[i] = null; + } + } + } + mBootAniZip = bootAni; + + try { + mAnimationParts = BootAnimationHelper.parseAnimation(mBootAniZip); + } catch (Exception e) { + // "Gotta catch 'em all" + Log.e(TAG, "Unable to set boot animation", e); + mAnimationParts = null; + mBootAniZip = null; + return false; + } + + if (mAnimationParts == null || mAnimationParts.size() == 0) { + return false; + } + + final BootAnimationHelper.AnimationPart part = mAnimationParts.get(0); + mCurrentPart = 0; + mCurrentPartPlayCount = part.playCount; + mFrameDuration = part.frameRateMillis; + mWriteBufferIndex = mReadBufferIndex = 0; + mCurrentFrame = 0; + + getNextFrame(); + + return true; + } + + private void getNextFrame() { + if (mAnimationParts == null || mAnimationParts.size() == 0) return; + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inBitmap = mBuffers[mWriteBufferIndex]; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + opts.inMutable = true; + final BootAnimationHelper.AnimationPart part = mAnimationParts.get(mCurrentPart); + try { + mBuffers[mWriteBufferIndex] = + BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( + part.frames.get(mCurrentFrame++))), null, opts); + } catch (IllegalArgumentException iae) { + // In case we're here because the bitmap could not be re-used, try creating a new one + opts.inBitmap = null; + try { + if (DEBUG) { + Log.d(TAG, "Trying to load frame without reusing existing bitmap", iae); + } + if (mBuffers[mWriteBufferIndex] != null) { + // clean up our old bitmap + mBuffers[mWriteBufferIndex].recycle(); + mBuffers[mWriteBufferIndex] = null; + } + mBuffers[mWriteBufferIndex] = + BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( + part.frames.get(mCurrentFrame++))), null, opts); + } catch (Exception e) { + // Still failling? Let's log it and carry on. + Log.w(TAG, "Unable to get next frame", e); + } + } catch (IOException e) { + Log.w(TAG, "Unable to get next frame", e); + } + mWriteBufferIndex = (mWriteBufferIndex + 1) % MAX_BUFFERS; + if (mCurrentFrame >= part.frames.size()) { + if (mCurrentPartPlayCount > 0) { + if (--mCurrentPartPlayCount == 0) { + mCurrentPart++; + if (mCurrentPart >= mAnimationParts.size()) mCurrentPart = 0; + mCurrentFrame = 0; + mCurrentPartPlayCount = mAnimationParts.get(mCurrentPart).playCount; + } else { + mCurrentFrame = 0; + } + } else { + mCurrentFrame = 0; + } + } + } + + public void start() { + if (mAnimationParts == null) return; + + mActive = true; + post(mUpdateAnimationRunnable); + } + + public void stop() { + mActive = false; + removeCallbacks(mUpdateImageRunnable); + removeCallbacks(mUpdateAnimationRunnable); + } + + private Runnable mUpdateAnimationRunnable = new Runnable() { + @Override + public void run() { + if (!mActive) return; + BootAniImageView.this.postDelayed(mUpdateAnimationRunnable, mFrameDuration); + if (!isOffScreen()) { + BootAniImageView.this.post(mUpdateImageRunnable); + mReadBufferIndex = (mReadBufferIndex + 1) % MAX_BUFFERS; + getNextFrame(); + } + } + }; + + private Runnable mUpdateImageRunnable = new Runnable() { + @Override + public void run() { + setImageBitmap(mBuffers[mReadBufferIndex]); + } + }; + + private boolean isOffScreen() { + int[] pos = new int[2]; + getLocationOnScreen(pos); + return pos[1] >= mContext.getResources().getDisplayMetrics().heightPixels; + } +} diff --git a/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java b/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java new file mode 100644 index 0000000..e00e67a --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.cyanogenmod.theme.chooser.R; + +public class ConfirmCancelOverlay extends FrameLayout { + + private View mAcceptButton; + private View mCancelButton; + private TextView mTitle; + + private OnOverlayDismissedListener mListener; + + public ConfirmCancelOverlay(Context context) { + this(context, null); + } + + public ConfirmCancelOverlay(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ConfirmCancelOverlay(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mAcceptButton = findViewById(R.id.accept); + mCancelButton = findViewById(R.id.cancel); + mTitle = (TextView) findViewById(R.id.overlay_title); + + mAcceptButton.setOnClickListener(mClickListener); + mCancelButton.setOnClickListener(mClickListener); + } + + public void setTitle(CharSequence title) { + mTitle.setText(title); + } + + public void setTitle(int resId) { + mTitle.setText(resId); + } + + public void setOnOverlayDismissedListener(OnOverlayDismissedListener listener) { + mListener = listener; + } + + public void dismiss() { + if (mListener != null) { + mListener.onDismissed(false); + } + } + + private OnClickListener mClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onDismissed(v == mAcceptButton); + } + } + }; + + public interface OnOverlayDismissedListener { + public void onDismissed(boolean accepted); + } +} diff --git a/src/org/cyanogenmod/theme/widget/FittedTextView.java b/src/org/cyanogenmod/theme/widget/FittedTextView.java new file mode 100644 index 0000000..2451d25 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/FittedTextView.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Paint; +import android.text.method.TransformationMethod; +import android.text.method.AllCapsTransformationMethod; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * Change the font size to match the measured + * textview size by width + * + */ +public class FittedTextView extends TextView { + private Paint mPaint; + //If set to true, the text will be resized to fit the view. + private boolean mAutoFitText = true; + //Used to instruct whether the text should be expanded to fill out the view, even if the text + //fits without being resized + private boolean mAutoExpand = true; + + public FittedTextView(Context context) { + this(context, null); + } + + public FittedTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FittedTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mPaint = new Paint(); + } + + protected void setAutoFitText(boolean autoFit) { + mAutoFitText = autoFit; + } + + protected boolean getAutoFitText() { + return mAutoFitText; + } + + protected void setAutoExpand(boolean autoExpand) { + mAutoExpand = autoExpand; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (!mAutoFitText) return; + + final float THRESHOLD = 0.5f; + final float TARGET_WIDTH = getMeasuredWidth(); + String text = getText().toString(); + TransformationMethod tm = getTransformationMethod(); + if (tm != null && tm instanceof AllCapsTransformationMethod) { + text = getText().toString().toUpperCase(); + } + mPaint.set(getPaint()); + + if (mPaint.measureText(text) <= TARGET_WIDTH && !mAutoExpand) return; + + float max = 200; + float min = 2; + while(max > min) { + float size = (max+min) / 2; + mPaint.setTextSize(size); + float measuredWidth = mPaint.measureText(text); + if (Math.abs(TARGET_WIDTH - measuredWidth) <= THRESHOLD) { + break; + } else if (measuredWidth > TARGET_WIDTH) { + max = size-1; + } else { + min = size+1; + } + } + this.setTextSize(TypedValue.COMPLEX_UNIT_PX, min-1); + } +} diff --git a/src/org/cyanogenmod/theme/widget/LatoTextView.java b/src/org/cyanogenmod/theme/widget/LatoTextView.java new file mode 100644 index 0000000..0cb02ae --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/LatoTextView.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.util.AttributeSet; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +import java.io.File; + +/** + * A custom TextView that always uses the Lato font + */ +public class LatoTextView extends FittedTextView { + private static final int NUM_TYPEFACE_PER_FAMILY = 4; + + private static final String FONT_ASSSET_DIR = "fonts"; + // Regular fonts + private static final String LATO_REGULAR_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Regular.ttf"; + private static final String LATO_REGULAR_BOLD_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegBold.ttf"; + private static final String LATO_REGULAR_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegItalic.otf"; + private static final String LATO_REGULAR_BOLD_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegBoldItalic.ttf"; + // Condensed fonts + private static final String LATO_CONDENSED_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Cond.ttf"; + private static final String LATO_CONDENSED_BOLD_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondBold.ttf"; + private static final String LATO_CONDENSED_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondItalic.ttf"; + private static final String LATO_CONDENSED_BOLD_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondBoldItalic.ttf"; + // Light fonts + private static final String LATO_LIGHT_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Light.ttf"; + private static final String LATO_LIGHT_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-LightItalic.ttf"; + + private static final String CONDENSED = "condensed"; + private static final String LIGHT = "light"; + + private static Typeface[] sLatoRegularTypeface; + private static Typeface[] sLatoCondensedTypeface; + private static Typeface[] sLatoLightTypeface; + private static final Object sLock = new Object(); + + // Retrieving these attributes is done via reflection so let's just do this once and share + // it amongst the other instances of LatoTextView + private static int[] sTextViewStyleAttributes; + + public LatoTextView(Context context) { + this(context, null); + } + + public LatoTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LatoTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + synchronized (sLock) { + AssetManager assets = context.getAssets(); + if (sLatoRegularTypeface == null) { + sLatoRegularTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoRegularTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_REGULAR_PATH); + sLatoRegularTypeface[Typeface.BOLD] = + Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_PATH); + sLatoRegularTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_REGULAR_ITALIC_PATH); + sLatoRegularTypeface[Typeface.BOLD_ITALIC] = + Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_ITALIC_PATH); + } + if (sLatoCondensedTypeface == null) { + sLatoCondensedTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoCondensedTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_CONDENSED_PATH); + sLatoCondensedTypeface[Typeface.BOLD] = + Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_PATH); + sLatoCondensedTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_CONDENSED_ITALIC_PATH); + sLatoCondensedTypeface[Typeface.BOLD_ITALIC] = + Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_ITALIC_PATH); + } + if (sLatoLightTypeface == null) { + sLatoLightTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoLightTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_LIGHT_PATH); + sLatoLightTypeface[Typeface.BOLD] = + sLatoRegularTypeface[Typeface.BOLD]; + sLatoLightTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_LIGHT_ITALIC_PATH); + sLatoLightTypeface[Typeface.BOLD_ITALIC] = + sLatoRegularTypeface[Typeface.BOLD_ITALIC]; + } + } + + final Resources.Theme theme = context.getTheme(); + if (sTextViewStyleAttributes == null) { + sTextViewStyleAttributes = + Utils.getResourceDeclareStyleableIntArray("com.android.internal", "TextView"); + } + + if (sTextViewStyleAttributes != null) { + TypedArray a = + theme.obtainStyledAttributes(attrs, sTextViewStyleAttributes, defStyle, 0); + String fontFamily = "sans-serif"; + int styleIndex = Typeface.NORMAL; + if (a != null) { + int n = a.getIndexCount(); + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + final Resources res = getResources(); + int attrFontFamily = + res.getIdentifier("TextView_fontFamily", "styleable", "android"); + int attrTextStyle = + res.getIdentifier("TextView_textStyle", "styleable", "android"); + if (attr == attrFontFamily) { + fontFamily = a.getString(attr); + } else if (attr == attrTextStyle) { + styleIndex = a.getInt(attr, styleIndex); + } + } + a.recycle(); + } + + setTypefaceFromAttrs(fontFamily, styleIndex); + setAutoExpand(false); + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, + R.styleable.FittedTextView, 0, 0); + try { + //Although we extend FittedTextView, we don't want all instances to auto fit the + //text, so we check if autoFitText has been set in the attributes. Default to false + boolean fit = styledAttrs.getBoolean(R.styleable.FittedTextView_autoFitText, false); + setAutoFitText(fit); + } finally { + styledAttrs.recycle(); + } + } + } + + private void setTypefaceFromAttrs(String familyName, int styleIndex) { + Typeface tf = null; + if (familyName != null) { + Typeface[] typefaces = sLatoRegularTypeface; + if (familyName.contains(CONDENSED)) { + typefaces = sLatoCondensedTypeface; + } else if (familyName.contains(LIGHT)) { + typefaces = sLatoLightTypeface; + } + tf = typefaces[styleIndex]; + if (tf != null) { + setTypeface(tf); + return; + } + } + setTypeface(tf, styleIndex); + } +} diff --git a/src/org/cyanogenmod/theme/widget/LockableScrollView.java b/src/org/cyanogenmod/theme/widget/LockableScrollView.java new file mode 100644 index 0000000..b4c9ffc --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/LockableScrollView.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ScrollView; + +public class LockableScrollView extends ScrollView { + private boolean mScrollingEnabled = true; + + public LockableScrollView(Context context) { + this(context, null); + } + + public LockableScrollView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LockableScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setScrollingEnabled(boolean enabled) { + mScrollingEnabled = enabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mScrollingEnabled && super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + return mScrollingEnabled && super.onTouchEvent(ev); + default: + return super.onTouchEvent(ev); + } + } + + @Override + public void setOverScrollMode(int mode) { + // Some themes can cause theme chooser to crash when creating the EdgeEffects for + // the scroll view. If an exception occurs we fallback to no overscroll + try { + super.setOverScrollMode(mode); + } catch (Exception e) { + super.setOverScrollMode(OVER_SCROLL_NEVER); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/NavBarSpace.java b/src/org/cyanogenmod/theme/widget/NavBarSpace.java new file mode 100644 index 0000000..720df29 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/NavBarSpace.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import org.cyanogenmod.theme.util.Utils; + +/** + * A simple view used to pad layouts so that content floats above the + * navigation bar. This is best used with transparent or translucent + * navigation bars where the content can go behind them. + */ +public class NavBarSpace extends View { + + public NavBarSpace(Context context) { + this(context, null); + } + + public NavBarSpace(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NavBarSpace(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (!Utils.hasNavigationBar(getContext())) { + this.setVisibility(View.GONE); + } else { + this.setVisibility(View.VISIBLE); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java b/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java new file mode 100644 index 0000000..18e3da2 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.cyanogenmod.theme.chooser.R; + +public class ThemeTagLayout extends LinearLayout { + private ImageView mAppliedTag; + private TextView mCustomizedTag; + private TextView mUpdatedTag; + private TextView mDefaultTag; + private TextView mLegacyTag; + + public ThemeTagLayout(Context context) { + this(context, null); + } + + public ThemeTagLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ThemeTagLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + LayoutInflater inflater = + (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mAppliedTag = (ImageView) inflater.inflate(R.layout.tag_applied, this, false); + mCustomizedTag = (TextView) inflater.inflate(R.layout.tag_customized, this, false); + mUpdatedTag = (TextView) inflater.inflate(R.layout.tag_updated, this, false); + mDefaultTag = (TextView) inflater.inflate(R.layout.tag_default, this, false); + mLegacyTag = (TextView) inflater.inflate(R.layout.tag_legacy, this, false); + } + + public void setAppliedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_applied) != null) return; + addView(mAppliedTag, 0); + } else { + if (findViewById(R.id.tag_applied) == null) return; + removeView(mAppliedTag); + } + } + + public void setCustomizedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_customized) != null) return; + final int childCount = getChildCount(); + if (childCount > 1) { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child != mAppliedTag) { + addView(mCustomizedTag, i); + break; + } + } + } else { + addView(mCustomizedTag); + } + } else { + if (findViewById(R.id.tag_customized) == null) return; + removeView(mCustomizedTag); + } + } + + public boolean isCustomizedTagEnabled() { + return findViewById(R.id.tag_customized) != null; + } + + public void setUpdatedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_updated) != null) return; + final int childCount = getChildCount(); + if (childCount > 2) { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child != mAppliedTag && child != mCustomizedTag) { + addView(mUpdatedTag, i); + break; + } + } + } else { + addView(mUpdatedTag); + } + } else { + if (findViewById(R.id.tag_updated) == null) return; + removeView(mUpdatedTag); + } + } + + public boolean isUpdatedTagEnabled() { + return findViewById(R.id.tag_updated) != null; + } + + public void setDefaultTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_default) != null) return; + addView(mDefaultTag); + } else { + if (findViewById(R.id.tag_default) == null) return; + removeView(mDefaultTag); + } + } + + public void setLegacyTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_legacy) != null) return; + addView(mLegacyTag); + } else { + if (findViewById(R.id.tag_legacy) == null) return; + removeView(mLegacyTag); + } + } +} |