Commit 0c5af647 authored by Romain Hunault's avatar Romain Hunault
Browse files

Merge branch 'sprint_delhi' into 'master'

[RELEASE] Sprint delhi

Closes #172 and #171

See merge request e/apps/BlissLauncher!20
parents a3cf67b6 e5efc5f0
Pipeline #37656 passed with stage
in 4 minutes and 34 seconds
......@@ -4,7 +4,7 @@ apply plugin: 'kotlin-android'
// Manifest version information!
def versionMajor = 1
def versionMinor = 3
def versionPatch = 0
def versionPatch = 1
android {
compileSdkVersion rootProject.ext.compileSdkVersion
......
......@@ -48,6 +48,7 @@
android:resumeWhilePausing="true"
android:screenOrientation="nosensor"
android:stateNotNeeded="true"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:theme="@style/HomeScreenTheme"
android:windowSoftInputMode="adjustPan">
<intent-filter>
......
......@@ -54,6 +54,10 @@ public class BlissLauncher extends Application {
return deviceProfile;
}
public void resetDeviceProfile() {
deviceProfile = new DeviceProfile(this);
}
public IconsHandler getIconsHandler() {
if (iconsPackHandler == null) {
iconsPackHandler = new IconsHandler(this);
......
......@@ -10,6 +10,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
......@@ -27,6 +28,7 @@ public class DeviceProfile {
public static Path path;
private final float widthCm;
private final float ratio;
private int statusBarHeight;
public int cellHeightWithoutPaddingPx;
public int hotseatCellHeightWithoutPaddingPx;
......@@ -157,6 +159,7 @@ public class DeviceProfile {
widthPx = realSize.x;
double x = widthPx / dm.xdpi;
ratio = dm.densityDpi / dm.xdpi;
widthCm = (float) (x * 2.540001f);
heightPx = realSize.y;
......@@ -231,7 +234,15 @@ public class DeviceProfile {
iconSizePx = 213;
}*/
float a = 1.578f;
float b = 1.23f;
Log.i(TAG, "updateIconSize: " + (int) a + " " + (int) b);
iconSizePx = (int) (widthPx / widthCm);
if (ratio >= 1) {
iconSizePx = iconSizePx * (int) ratio;
}
iconTextSizePx = (int) (Utilities.pxFromSp(12, dm) * scale);
iconDrawablePaddingPx = (availableWidthPx - iconSizePx * 4) / 5;
......
......@@ -229,6 +229,12 @@ public class Utilities {
return defaultValue;
}
/**
* Ensures that a value is within given bounds. Specifically:
* If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
* return upperBound; else return value unchanged.
*/
public static int boundToRange(int value, int lowerBound, int upperBound) {
return Math.max(lowerBound, Math.min(value, upperBound));
}
}
......@@ -23,12 +23,13 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import foundation.e.blisslauncher.BlissLauncher;
import foundation.e.blisslauncher.R;
import foundation.e.blisslauncher.features.launcher.DetectSwipeGestureListener;
import foundation.e.blisslauncher.features.launcher.LauncherActivity;
import foundation.e.blisslauncher.features.launcher.OnSwipeDownListener;
public class HorizontalPager extends ViewGroup implements Insettable{
public class HorizontalPager extends ViewGroup implements Insettable {
private static final String TAG = "HorizontalPager";
private static final int INVALID_SCREEN = -1;
public static final int SPEC_UNDEFINED = -1;
......@@ -63,6 +64,7 @@ public class HorizontalPager extends ViewGroup implements Insettable{
private boolean mIsUiCreated;
private GestureDetectorCompat gestureDetectorCompat;
private WindowInsets insets;
private float mLastMotionRawY;
public HorizontalPager(Context context, AttributeSet attrs) {
this(context, attrs, 0);
......@@ -276,6 +278,9 @@ public class HorizontalPager extends ViewGroup implements Insettable{
final float x = ev.getX();
final float y = ev.getY();
mLastMotionRawY = ev.getRawY();
switch (action) {
case MotionEvent.ACTION_MOVE:
/*
......@@ -329,7 +334,7 @@ public class HorizontalPager extends ViewGroup implements Insettable{
if (xMoved || yMoved) {
if (yMoved && (y - mLastMotionY) > 0 && yDiff > xDiff && currentPage != 0) {
if (yMoved && (y - mLastMotionY) > 0 && yDiff > xDiff && inThresholdRegion() && currentPage != 0) {
mTouchState = TOUCH_STATE_VERTICAL_SCROLLING;
((OnSwipeDownListener) getContext()).onSwipeStart();
} else if (xMoved && yDiff < xDiff) {
......@@ -351,6 +356,10 @@ public class HorizontalPager extends ViewGroup implements Insettable{
}
}
private boolean inThresholdRegion() {
return (mLastMotionRawY / BlissLauncher.getApplication(getContext()).getDeviceProfile().availableHeightPx) > (float) 1 / 5;
}
void enableChildrenCache() {
setChildrenDrawingCacheEnabled(true);
}
......@@ -539,13 +548,14 @@ public class HorizontalPager extends ViewGroup implements Insettable{
setLayoutParams(lp);
updateInsetsForChildren();
this.insets = insets;
postInvalidate();
}
private void updateInsetsForChildren() {
int childCount = getChildCount();
for (int index = 0; index < childCount; ++index){
for (int index = 0; index < childCount; ++index) {
View child = getChildAt(index);
if(child instanceof Insettable) {
if (child instanceof Insettable) {
Log.d(TAG, "child is instance of insettable");
((Insettable) child).setInsets(insets);
}
......
......@@ -8,19 +8,23 @@ import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.RelativeLayout;
import foundation.e.blisslauncher.BlissLauncher;
import foundation.e.blisslauncher.R;
public class InsettableRelativeLayout extends RelativeLayout {
private final Context mContext;
protected WindowInsets mInsets;
public InsettableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
BlissLauncher.getApplication(mContext).resetDeviceProfile();
updateChildInsets(insets);
mInsets = new WindowInsets(insets);
return insets;
......
package foundation.e.blisslauncher.core.customviews;
import android.animation.LayoutTransition;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import foundation.e.blisslauncher.core.customviews.pageindicators.PageIndicatorDots;
import foundation.e.blisslauncher.features.launcher.LauncherActivity;
public class Workspace extends PagedView<PageIndicatorDots> implements View.OnTouchListener{
private static final String TAG = "Workspace";
private static final int DEFAULT_PAGE = 0;
private final LauncherActivity mLauncher;
private LayoutTransition mLayoutTransition;
public Workspace(Context context, AttributeSet attributeSet) {
this(context, attributeSet, 0);
}
public Workspace(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
mLauncher = LauncherActivity.getLauncher(context);
setHapticFeedbackEnabled(false);
initWorkspace();
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
}
private void initWorkspace() {
mCurrentPage = DEFAULT_PAGE;
setClipToPadding(false);
setupLayoutTransition();
//setWallpaperDimension();
}
private void setupLayoutTransition() {
// We want to show layout transitions when pages are deleted, to close the gap.
mLayoutTransition = new LayoutTransition();
mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
setLayoutTransition(mLayoutTransition);
}
void enableLayoutTransitions() {
setLayoutTransition(mLayoutTransition);
}
void disableLayoutTransitions() {
setLayoutTransition(null);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
}
package foundation.e.blisslauncher.core.customviews.pageindicators
/**
* Base class for a page indicator.
*/
interface PageIndicator {
fun setScroll(currentScroll: Int, totalScroll: Int)
fun setActiveMarker(activePage: Int)
fun setMarkersCount(numMarkers: Int)
}
\ No newline at end of file
package foundation.e.blisslauncher.core.customviews.pageindicators
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Property
import android.view.View
import android.view.ViewOutlineProvider
import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator
import foundation.e.blisslauncher.R
import kotlin.math.abs
/**
* [PageIndicator] which shows dots per page. The active page is shown with the current
* accent color.
*/
class PageIndicatorDots(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
View(context, attrs, defStyleAttr), PageIndicator {
private val mCirclePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val mDotRadius: Float
private val mActiveColor: Int
private val mInActiveColor: Int
private val mIsRtl: Boolean = false
private var mNumPages = 0
private var mActivePage = 0
/**
* The current position of the active dot including the animation progress.
* For ex:
* 0.0 => Active dot is at position 0
* 0.33 => Active dot is at position 0 and is moving towards 1
* 0.50 => Active dot is at position [0, 1]
* 0.77 => Active dot has left position 0 and is collapsing towards position 1
* 1.0 => Active dot is at position 1
*/
private var mCurrentPosition = 0f
private var mFinalPosition = 0f
private var mAnimator: ObjectAnimator? = null
private var mEntryAnimationRadiusFactors: FloatArray? = null
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
override fun setScroll(currentScroll: Int, totalScroll: Int) {
var currentScroll = currentScroll
if (mNumPages > 1) {
// Ignore this as of now.
if (mIsRtl) {
currentScroll = totalScroll - currentScroll
}
val scrollPerPage = totalScroll / (mNumPages - 1)
val pageToLeft = currentScroll / scrollPerPage
val pageToLeftScroll = pageToLeft * scrollPerPage
val pageToRightScroll = pageToLeftScroll + scrollPerPage
val scrollThreshold =
SHIFT_THRESHOLD * scrollPerPage
when {
currentScroll < pageToLeftScroll + scrollThreshold -> { // scroll is within the left page's threshold
animateToPosition(pageToLeft.toFloat())
}
currentScroll > pageToRightScroll - scrollThreshold -> { // scroll is far enough from left page to go to the right page
animateToPosition(pageToLeft + 1.toFloat())
}
else -> { // scroll is between left and right page
animateToPosition(pageToLeft + SHIFT_PER_ANIMATION)
}
}
}
}
private fun animateToPosition(position: Float) {
mFinalPosition = position
if (abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
mCurrentPosition = mFinalPosition
}
if (mAnimator == null && mCurrentPosition.compareTo(mFinalPosition) != 0) {
val positionForThisAnim =
if (mCurrentPosition > mFinalPosition) mCurrentPosition - SHIFT_PER_ANIMATION else mCurrentPosition + SHIFT_PER_ANIMATION
mAnimator = ObjectAnimator.ofFloat(
this,
CURRENT_POSITION,
positionForThisAnim
).apply {
addListener(AnimationCycleListener())
duration = ANIMATION_DURATION
}
mAnimator?.start()
}
}
fun stopAllAnimations() {
if (mAnimator != null) {
mAnimator!!.cancel()
mAnimator = null
}
mFinalPosition = mActivePage.toFloat()
CURRENT_POSITION.set(this, mFinalPosition)
}
/**
* Sets up up the page indicator to play the entry animation.
* [.playEntryAnimation] must be called after this.
*/
fun prepareEntryAnimation() {
mEntryAnimationRadiusFactors = FloatArray(mNumPages)
invalidate()
}
fun playEntryAnimation() {
val count = mEntryAnimationRadiusFactors!!.size
if (count == 0) {
mEntryAnimationRadiusFactors = null
invalidate()
return
}
val interpolator: Interpolator =
OvershootInterpolator(ENTER_ANIMATION_OVERSHOOT_TENSION)
val animSet = AnimatorSet()
for (i in 0 until count) {
val anim = ValueAnimator.ofFloat(0f, 1f)
.setDuration(ENTER_ANIMATION_DURATION.toLong())
anim!!.addUpdateListener { animation ->
mEntryAnimationRadiusFactors!![i] =
(animation!!.animatedValue as Float)
invalidate()
}
anim.interpolator = interpolator
anim.startDelay =
ENTER_ANIMATION_START_DELAY + ENTER_ANIMATION_STAGGERED_DELAY * i.toLong()
animSet.play(anim)
}
animSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
mEntryAnimationRadiusFactors = null
invalidateOutline()
invalidate()
}
})
animSet.start()
}
override fun setActiveMarker(activePage: Int) {
if (mActivePage != activePage) {
mActivePage = activePage
}
}
override fun setMarkersCount(numMarkers: Int) {
mNumPages = numMarkers
requestLayout()
}
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) { // Add extra spacing of mDotRadius on all sides so that entry animation could be run.
val width =
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) MeasureSpec.getSize(
widthMeasureSpec
) else ((mNumPages * 3 + 2) * mDotRadius).toInt()
val height =
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) MeasureSpec.getSize(
heightMeasureSpec
) else (4 * mDotRadius).toInt()
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) { // Draw all page indicators;
var circleGap = 3 * mDotRadius
val startX = (width - mNumPages * circleGap + mDotRadius) / 2
var x = startX + mDotRadius
val y = height / 2.toFloat()
if (mEntryAnimationRadiusFactors != null) { // During entry animation, only draw the circles
if (mIsRtl) {
x = getWidth() - x
circleGap = -circleGap
}
for (i in mEntryAnimationRadiusFactors!!.indices) {
mCirclePaint.setColor(if (i == mActivePage) mActiveColor else mInActiveColor)
canvas.drawCircle(
x,
y,
mDotRadius * mEntryAnimationRadiusFactors!![i],
mCirclePaint
)
x += circleGap
}
} else {
mCirclePaint.color = mInActiveColor
for (i in 0 until mNumPages) {
canvas.drawCircle(x, y, mDotRadius, mCirclePaint)
x += circleGap
}
mCirclePaint.setColor(mActiveColor)
canvas.drawRoundRect(activeRect, mDotRadius, mDotRadius, mCirclePaint)
}
} // Dot is leaving the left circle.
// dot is capturing the right circle.
private val activeRect: RectF?
get() {
val startCircle: Float = mCurrentPosition
var delta = mCurrentPosition - startCircle
val diameter = 2 * mDotRadius
val circleGap = 3 * mDotRadius
val startX = (width - mNumPages * circleGap + mDotRadius) / 2
sTempRect!!.top = height * 0.5f - mDotRadius
sTempRect.bottom = height * 0.5f + mDotRadius
sTempRect.left = startX + startCircle * circleGap
sTempRect.right =
sTempRect.left + diameter
if (delta < SHIFT_PER_ANIMATION) { // dot is capturing the right circle.
sTempRect.right += delta * circleGap * 2
} else { // Dot is leaving the left circle.
sTempRect.right += circleGap
delta -= SHIFT_PER_ANIMATION
sTempRect.left += delta * circleGap * 2
}
if (mIsRtl) {
val rectWidth = sTempRect.width()
sTempRect.right =
width - sTempRect.left
sTempRect.left =
sTempRect.right - rectWidth
}
return sTempRect
}
private inner class MyOutlineProver : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
if (mEntryAnimationRadiusFactors == null) {
val activeRect = activeRect
outline!!.setRoundRect(
activeRect!!.left.toInt(),
activeRect.top.toInt(),
activeRect.right.toInt(),
activeRect.bottom.toInt(),
mDotRadius
)
}
}
}
/**
* Listener for keep running the animation until the final state is reached.
*/
private inner class AnimationCycleListener : AnimatorListenerAdapter() {
private var mCancelled = false
override fun onAnimationCancel(animation: Animator?) {
mCancelled = true
}
override fun onAnimationEnd(animation: Animator?) {
if (!mCancelled) {
mAnimator = null
animateToPosition(mFinalPosition)
}
}
}
companion object {
private const val SHIFT_PER_ANIMATION = 0.5f
private const val SHIFT_THRESHOLD = 0.1f
private const val ANIMATION_DURATION: Long = 150
private const val ENTER_ANIMATION_START_DELAY = 300
private const val ENTER_ANIMATION_STAGGERED_DELAY = 150
private const val ENTER_ANIMATION_DURATION = 400
// This value approximately overshoots to 1.5 times the original size.
private const val ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f
private val sTempRect: RectF? = RectF()
private val CURRENT_POSITION: Property<PageIndicatorDots, Float> =
object : Property<PageIndicatorDots, Float>(
Float::class.java, "current_position"
) {
override fun get(obj: PageIndicatorDots): Float {
return obj.mCurrentPosition
}
override fun set(obj: PageIndicatorDots, pos: Float) {
obj.mCurrentPosition = pos
obj.invalidate()
obj.invalidateOutline()
}
}
}
init {
mCirclePaint.style = Paint.Style.FILL
mDotRadius = resources.getDimension(R.dimen.dotSize) / 2
outlineProvider = MyOutlineProver()
mActiveColor = resources.getColor(R.color.dot_on_color)
mInActiveColor = resources.getColor(R.color.dot_on_color)
//mIsRtl = Utilities.isRtl(getResources())
}
}
\ No newline at end of file