diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt index 145e2e8..751843f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -61,6 +61,8 @@ class MainActivity : SimpleActivity(), FlingListener { private var mMoveGestureThreshold = 0 private var mIgnoreUpEvent = false private var mIgnoreMoveEvents = false + private var mIgnoreXMoveEvents = false + private var mIgnoreYMoveEvents = false private var mLongPressedIcon: HomeScreenGridItem? = null private var mOpenPopupMenu: PopupMenu? = null private var mCachedLaunchers = ArrayList() @@ -290,13 +292,20 @@ class MainActivity : SimpleActivity(), FlingListener { if (hasFingerMoved && !mIgnoreMoveEvents) { val diffY = mTouchDownY - event.y + val diffX = mTouchDownX - event.x - if (isWidgetsFragmentExpanded()) { - val newY = mWidgetsFragmentY - diffY - binding.widgetsFragment.root.y = Math.min(Math.max(0f, newY), mScreenHeight.toFloat()) - } else if (mLongPressedIcon == null) { - val newY = mAllAppsFragmentY - diffY - binding.allAppsFragment.root.y = Math.min(Math.max(0f, newY), mScreenHeight.toFloat()) + if (abs(diffY) > abs(diffX) && !mIgnoreYMoveEvents) { + mIgnoreXMoveEvents = true + if (isWidgetsFragmentExpanded()) { + val newY = mWidgetsFragmentY - diffY + binding.widgetsFragment.root.y = Math.min(Math.max(0f, newY), mScreenHeight.toFloat()) + } else if (mLongPressedIcon == null) { + val newY = mAllAppsFragmentY - diffY + binding.allAppsFragment.root.y = Math.min(Math.max(0f, newY), mScreenHeight.toFloat()) + } + } else if (abs(diffX) > abs(diffY) && !mIgnoreXMoveEvents) { + mIgnoreYMoveEvents = true + binding.homeScreenGrid.root.setSwipeMovement(diffX) } } @@ -314,18 +323,27 @@ class MainActivity : SimpleActivity(), FlingListener { binding.homeScreenGrid.root.itemDraggingStopped() if (!mIgnoreUpEvent) { - if (binding.allAppsFragment.root.y < mScreenHeight * 0.5) { - showFragment(binding.allAppsFragment) - } else if (isAllAppsFragmentExpanded()) { - hideFragment(binding.allAppsFragment) + if (!mIgnoreYMoveEvents) { + if (binding.allAppsFragment.root.y < mScreenHeight * 0.5) { + showFragment(binding.allAppsFragment) + } else if (isAllAppsFragmentExpanded()) { + hideFragment(binding.allAppsFragment) + } + + if (binding.widgetsFragment.root.y < mScreenHeight * 0.5) { + showFragment(binding.widgetsFragment) + } else if (isWidgetsFragmentExpanded()) { + hideFragment(binding.widgetsFragment) + } } - if (binding.widgetsFragment.root.y < mScreenHeight * 0.5) { - showFragment(binding.widgetsFragment) - } else if (isWidgetsFragmentExpanded()) { - hideFragment(binding.widgetsFragment) + if (!mIgnoreXMoveEvents) { + binding.homeScreenGrid.root.finalizeSwipe() } } + + mIgnoreXMoveEvents = false + mIgnoreYMoveEvents = false } } @@ -743,6 +761,10 @@ class MainActivity : SimpleActivity(), FlingListener { } override fun onFlingUp() { + if (mIgnoreYMoveEvents) { + return + } + if (!isWidgetsFragmentExpanded()) { mIgnoreUpEvent = true showFragment(binding.allAppsFragment) @@ -751,6 +773,10 @@ class MainActivity : SimpleActivity(), FlingListener { @SuppressLint("WrongConstant") override fun onFlingDown() { + if (mIgnoreYMoveEvents) { + return + } + mIgnoreUpEvent = true if (isAllAppsFragmentExpanded()) { hideFragment(binding.allAppsFragment) @@ -765,11 +791,19 @@ class MainActivity : SimpleActivity(), FlingListener { } override fun onFlingRight() { + if (mIgnoreXMoveEvents) { + return + } + mIgnoreUpEvent = true binding.homeScreenGrid.root.prevPage(redraw = true) } override fun onFlingLeft() { + if (mIgnoreXMoveEvents) { + return + } + mIgnoreUpEvent = true binding.homeScreenGrid.root.nextPage(redraw = true) } diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt index 07b4e15..067f476 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -12,6 +12,7 @@ import android.graphics.* import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.Bundle +import android.os.Handler import android.text.Layout import android.text.StaticLayout import android.text.TextPaint @@ -75,13 +76,15 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var redrawWidgets = false private var iconSize = 0 - private var lastPage = 0 - private var currentPage = 0 - private var pageChangeLastArea = PageChangeArea.MIDDLE - private var pageChangeLastAreaEntryTime = 0L - private var pageChangeAnimLeftPercentage = 0f - private var pageChangeEnabled = true - private var pageChangeIndicatorsAlpha = 0f + private val pager = AnimatedGridPager( + getMaxPage = ::getMaxPage, + redrawGrid = ::redrawGrid, + getWidth = { width }, + getHandler = { handler }, + getNextPageBound = { right - sideMargins.right - cellWidth / 2 }, + getPrevPageBound = { left + sideMargins.left + cellWidth / 2 }, + pageChangeStarted = { closeFolder() } + ) private var currentlyOpenFolder: HomeScreenFolder? = null private var draggingLeftFolderAt: Long? = null @@ -101,26 +104,6 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel var itemClickListener: ((HomeScreenGridItem) -> Unit)? = null var itemLongClickListener: ((HomeScreenGridItem) -> Unit)? = null - private val checkAndExecuteDelayedPageChange: Runnable = Runnable { - if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) { - when (pageChangeLastArea) { - PageChangeArea.RIGHT -> nextOrAdditionalPage(true) - PageChangeArea.LEFT -> prevPage(true) - else -> clearPageChangeFlags() - } - } - } - - private val startFadingIndicators: Runnable = Runnable { - ValueAnimator.ofFloat(1f, 0f) - .apply { - addUpdateListener { - pageChangeIndicatorsAlpha = it.animatedValue as Float - redrawGrid() - } - start() - } - } init { ViewCompat.setAccessibilityDelegate(this, HomeScreenGridTouchHelper(this)) @@ -242,7 +225,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } gridItems.removeIf { it.id == item.id } - if (currentPage > getMaxPage()) { + if (pager.isOutsideOfPageRange()) { post { prevPage() } @@ -300,8 +283,6 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - pageChangeIndicatorsAlpha = 1f - removeCallbacks(startFadingIndicators) if (draggedItemCurrentCoords.first == -1 && draggedItemCurrentCoords.second == -1 && draggedItem != null) { if (draggedItem!!.type == ITEM_TYPE_WIDGET) { val draggedWidgetView = widgetViews.firstOrNull { it.tag == draggedItem?.widgetId } @@ -343,47 +324,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel draggingEnteredNewFolderAt = null } - if (x > right - sideMargins.right - cellWidth / 2) { - doWithPageChangeDelay(PageChangeArea.RIGHT) { - nextOrAdditionalPage() - } - } else if (x < left + sideMargins.left + cellWidth / 2) { - doWithPageChangeDelay(PageChangeArea.LEFT) { - prevPage() - } - } else { - clearPageChangeFlags() - } + pager.handleItemMovement(x, y) redrawGrid() } - private fun clearPageChangeFlags() { - pageChangeLastArea = PageChangeArea.MIDDLE - pageChangeLastAreaEntryTime = 0 - removeCallbacks(checkAndExecuteDelayedPageChange) - } - - private fun schedulePageChange() { - pageChangeLastAreaEntryTime = System.currentTimeMillis() - postDelayed(checkAndExecuteDelayedPageChange, PAGE_CHANGE_HOLD_THRESHOLD) - } - - private fun scheduleIndicatorsFade() { - pageChangeIndicatorsAlpha = 1f - postDelayed(startFadingIndicators, PAGE_INDICATORS_FADE_DELAY) - } - - private fun doWithPageChangeDelay(needed: PageChangeArea, pageChangeFunction: () -> Boolean) { - if (pageChangeLastArea != needed) { - pageChangeLastArea = needed - schedulePageChange() - } else if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) { - if (pageChangeFunction()) { - clearPageChangeFlags() - } - } - } - // figure out at which cell was the item dropped, if it is empty fun itemDraggingStopped() { widgetViews.forEach { @@ -394,7 +338,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return } - scheduleIndicatorsFade() + pager.itemMovementStopped() when (draggedItem!!.type) { ITEM_TYPE_FOLDER -> moveItem() ITEM_TYPE_ICON, ITEM_TYPE_SHORTCUT -> addAppIconOrShortcut() @@ -496,7 +440,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel top = yIndex right = xIndex bottom = yIndex - page = currentPage + page = pager.getCurrentPage() docked = yIndex == rowCount - 1 ensureBackgroundThread { @@ -659,7 +603,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel top = yIndex right = finalXIndex bottom = yIndex - page = currentPage + page = pager.getCurrentPage() docked = yIndex == rowCount - 1 parentId = newParentId @@ -699,7 +643,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel yIndex, finalXIndex, yIndex, - currentPage, + pager.getCurrentPage(), draggedItem!!.packageName, draggedItem!!.activityName, draggedItem!!.title, @@ -790,7 +734,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel top = widgetRect.top right = widgetRect.right bottom = widgetRect.bottom - page = currentPage + page = pager.getCurrentPage() } ensureBackgroundThread { @@ -807,7 +751,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel widgetItem.top, widgetItem.right, widgetItem.bottom, - currentPage, + pager.getCurrentPage(), false, null, widgetItem.id!! @@ -867,7 +811,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel removeItemFromHomeScreen(item) } - if (currentPage > getMaxPage()) { + if (pager.isOutsideOfPageRange()) { prevPage(redraw = true) } } @@ -902,25 +846,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun updateWidgetPositionAndSize(widgetView: AppWidgetHostView, item: HomeScreenGridItem): Size { - var x = calculateWidgetX(item.left) + width * item.page - width * lastPage - if (pageChangeAnimLeftPercentage > 0f && pageChangeAnimLeftPercentage < 1f && (item.page == currentPage || item.page == lastPage)) { - val xFactor = if (currentPage > lastPage) { - pageChangeAnimLeftPercentage - } else { - -pageChangeAnimLeftPercentage - } - val lastXFactor = if (currentPage > lastPage) { - pageChangeAnimLeftPercentage - 1 - } else { - 1 - pageChangeAnimLeftPercentage - } - if (item.page == currentPage) { - x += width * xFactor - } - if (item.page == lastPage) { - x += width * lastXFactor - } - } + val currentViewPosition = pager.getCurrentViewPositionInFullPageSpace() * width.toFloat() + val x = calculateWidgetX(item.left) + width * item.page - currentViewPosition widgetView.x = x widgetView.y = calculateWidgetY(item.top) val widgetWidth = item.getWidthInCells() * cellWidth @@ -977,16 +904,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fillCellSizes() } - val currentXFactor = if (currentPage > lastPage) { - pageChangeAnimLeftPercentage - } else { - -pageChangeAnimLeftPercentage - } - val lastXFactor = if (currentPage > lastPage) { - pageChangeAnimLeftPercentage - 1 - } else { - 1 - pageChangeAnimLeftPercentage - } + val currentXFactor = pager.getXFactorForCurrentPage() + val lastXFactor = pager.getXFactorForLastPage() fun handleGridItemDrawing(item: HomeScreenGridItem, baseItemX: Int, baseItemY: Int) { if (item.id != draggedItem?.id) { @@ -1040,7 +959,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel ) } - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && it.page == currentPage && !it.docked && it.parentId == null } + gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && pager.isItemOnCurrentPage(it) && !it.docked && it.parentId == null } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -1056,8 +975,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel handleMainGridItemDrawing(item, 0f) } - if (pageChangeAnimLeftPercentage > 0f) { - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && it.page == lastPage && !it.docked && it.parentId == null } + if (pager.isAnimatingPageChange()) { + gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && pager.isItemOnLastPage(it) && !it.docked && it.parentId == null } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -1067,6 +986,21 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + if (pager.isSwiped()) { + gridItems.filter { + (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) + && pager.isItemInSwipeRange(it) + && !it.docked + && it.parentId == null + }.forEach { item -> + if (item.outOfBounds()) { + return@forEach + } + + handleMainGridItemDrawing(item, lastXFactor) + } + } + if (isFirstDraw) { gridItems.filter { it.type == ITEM_TYPE_WIDGET && !it.outOfBounds() }.forEach { item -> bindWidget(item, true) @@ -1080,19 +1014,15 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } // Only draw page indicators when there is a need for it - if (pageChangeAnimLeftPercentage > 0f || pageChangeIndicatorsAlpha != 0f) { - val pageCount = max(getMaxPage(), currentPage) + 1 + if (pager.shouldDisplayPageChangeIndicator()) { + val pageCount = pager.getPageCount() val pageIndicatorsRequiredWidth = pageCount * pageIndicatorRadius * 2 + pageCount * (pageIndicatorMargin - 1) val usableWidth = getFakeWidth() val pageIndicatorsStart = (usableWidth - pageIndicatorsRequiredWidth) / 2 + sideMargins.left var currentPageIndicatorLeft = pageIndicatorsStart val pageIndicatorY = cellYCoords[rowCount - 1].toFloat() + sideMargins.top + extraYMargin + iconMargin val pageIndicatorStep = pageIndicatorRadius * 2 + pageIndicatorMargin - if (pageChangeIndicatorsAlpha != 0f) { - emptyPageIndicatorPaint.alpha = (pageChangeIndicatorsAlpha * 255.0f).toInt() - } else { - emptyPageIndicatorPaint.alpha = 255 - } + emptyPageIndicatorPaint.alpha = pager.getPageChangeIndicatorsAlpha() // Draw empty page indicators for (page in 0 until pageCount) { canvas.drawCircle(currentPageIndicatorLeft + pageIndicatorRadius, pageIndicatorY, pageIndicatorRadius, emptyPageIndicatorPaint) @@ -1100,14 +1030,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } // Draw current page indicator on exact position - val currentIndicatorRangeStart = pageIndicatorsStart + lastPage * pageIndicatorStep - val currentIndicatorRangeEnd = pageIndicatorsStart + currentPage * pageIndicatorStep - val currentIndicatorPosition = MathUtils.lerp(currentIndicatorRangeStart, currentIndicatorRangeEnd, 1 - pageChangeAnimLeftPercentage) - if (pageChangeIndicatorsAlpha != 0f) { - currentPageIndicatorPaint.alpha = (pageChangeIndicatorsAlpha * 255.0f).toInt() - } else { - currentPageIndicatorPaint.alpha = 255 - } + val currentIndicatorPosition = pageIndicatorsStart + pager.getCurrentViewPositionInFullPageSpace() * pageIndicatorStep + currentPageIndicatorPaint.alpha = pager.getPageChangeIndicatorsAlpha() canvas.drawCircle(currentIndicatorPosition + pageIndicatorRadius, pageIndicatorY, pageIndicatorRadius, currentPageIndicatorPaint) } @@ -1117,6 +1041,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val folderRect = folder.getDrawingRect() val folderItemsRect = folder.getItemsDrawingRect() + val currentViewPosition = pager.getCurrentViewPositionInFullPageSpace() * width.toFloat() + val rectOffset = width * folder.item.page - currentViewPosition + folderRect.offset(rectOffset, 0f) + canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) canvas.withScale(folder.scale, folder.scale, folderRect.centerX(), folderRect.centerY()) { @@ -1427,48 +1355,29 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun getMaxPage() = gridItems.filter { !it.docked && !it.outOfBounds() }.maxOfOrNull { it.page } ?: 0 - private fun nextOrAdditionalPage(redraw: Boolean = false): Boolean { - if (currentPage < getMaxPage() + 1 && pageChangeEnabled) { - lastPage = currentPage - currentPage++ - handlePageChange(redraw) - return true - } - - return false - } - fun nextPage(redraw: Boolean = false): Boolean { - if (currentPage < getMaxPage() && pageChangeEnabled) { - lastPage = currentPage - currentPage++ - handlePageChange(redraw) - return true - } - - return false + return pager.nextPage(redraw) } fun prevPage(redraw: Boolean = false): Boolean { - if (currentPage > 0 && pageChangeEnabled) { - lastPage = currentPage - currentPage-- - handlePageChange(redraw) - return true - } - - return false + return pager.prevPage(redraw) } fun skipToPage(targetPage: Int): Boolean { - if (currentPage != targetPage && targetPage < getMaxPage() + 1) { - lastPage = currentPage - currentPage = targetPage - handlePageChange() - return true - } + return pager.skipToPage(targetPage) + } - return false + fun getCurrentIconSize(): Int = iconSize + + + fun setSwipeMovement(diffX: Float) { + if (draggedItem == null) { + pager.setSwipeMovement(diffX) + } + } + + fun finalizeSwipe() { + pager.finalizeSwipe() } fun openFolder(folder: HomeScreenGridItem) { @@ -1489,42 +1398,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - fun getCurrentIconSize(): Int = iconSize - - fun getCurrentCellSize(): Int = cellWidth - - fun getCurrentCellMargin(): Int = iconMargin - - private fun handlePageChange(redraw: Boolean = false) { - pageChangeEnabled = false - pageChangeIndicatorsAlpha = 0f - closeFolder() - removeCallbacks(startFadingIndicators) - if (redraw) { - redrawGrid() - } - ValueAnimator.ofFloat(1f, 0f) - .apply { - addUpdateListener { - pageChangeAnimLeftPercentage = it.animatedValue as Float - redrawGrid() - } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - super.onAnimationEnd(animation) - pageChangeAnimLeftPercentage = 0f - pageChangeEnabled = true - lastPage = currentPage - schedulePageChange() - scheduleIndicatorsFade() - redrawGrid() - } - }) - start() - } - } - - private fun ArrayList.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null } + private fun ArrayList.filterVisibleOnly() = filter { (pager.isItemOnCurrentPage(it) || it.docked) && it.parentId == null } private fun HomeScreenGridItem.toFolder(animateOpening: Boolean = false) = HomeScreenFolder(this, animateOpening) @@ -1533,6 +1407,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel animateOpening: Boolean ) { var scale: Float = 1f + private var closing = false init { if (animateOpening) { @@ -1659,6 +1534,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fun animateClosing(callback: () -> Unit) { post { + if (closing) { + return@post + } + closing = true ValueAnimator.ofFloat(scale, 0.2f) .apply { interpolator = DecelerateInterpolator() @@ -1695,3 +1574,291 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } +/** + * Helper class responsible for managing current page and providing utilities for animating page changes, + * as well as partial dragigng between pages + */ +private class AnimatedGridPager( + private val getMaxPage: () -> Int, + private val redrawGrid: () -> Unit, + private val getWidth: () -> Int, + private val getHandler: () -> Handler, + private val getNextPageBound: () -> Int, + private val getPrevPageBound: () -> Int, + private val pageChangeStarted: () -> Unit +) { + + companion object { + private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L + private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L + + private enum class PageChangeArea { + LEFT, + MIDDLE, + RIGHT + } + } + + private var lastPage = 0 + private var currentPage = 0 + private var pageChangeLastArea = PageChangeArea.MIDDLE + private var pageChangeLastAreaEntryTime = 0L + private var pageChangeAnimLeftPercentage = 0f + private var pageChangeEnabled = true + private var pageChangeIndicatorsAlpha = 0f + private var pageChangeSwipedPercentage = 0f + + fun getCurrentPage() = currentPage + + fun isItemOnCurrentPage(item: HomeScreenGridItem) = item.page == currentPage + + fun isItemOnLastPage(item: HomeScreenGridItem) = item.page == lastPage + + fun getPageCount() = max(getMaxPage(), currentPage) + 1 + + + fun isOutsideOfPageRange() = currentPage > getMaxPage() + + fun isItemInSwipeRange(item: HomeScreenGridItem) = + ((pageChangeSwipedPercentage > 0f && item.page == currentPage - 1) || (pageChangeSwipedPercentage < 0f && item.page == currentPage + 1)) + + fun isSwiped() = abs(pageChangeSwipedPercentage) > 0f + + fun isAnimatingPageChange() = pageChangeAnimLeftPercentage > 0f + + fun shouldDisplayPageChangeIndicator() = isSwiped() || isAnimatingPageChange() || pageChangeIndicatorsAlpha > 0f + + fun getPageChangeIndicatorsAlpha() = if (pageChangeIndicatorsAlpha != 0f) { + (pageChangeIndicatorsAlpha * 255.0f).toInt() + } else { + 255 + } + + fun getXFactorForCurrentPage(): Float { + return if (abs(pageChangeSwipedPercentage) > 0f) { + pageChangeSwipedPercentage + } else { + if (currentPage > lastPage) { + pageChangeAnimLeftPercentage + } else { + -pageChangeAnimLeftPercentage + } + } + } + + fun getXFactorForLastPage(): Float { + return if (abs(pageChangeSwipedPercentage) > 0f) { + (1 - abs(pageChangeSwipedPercentage)) * -sign(pageChangeSwipedPercentage) + } else { + if (currentPage > lastPage) { + pageChangeAnimLeftPercentage - 1 + } else { + 1 - pageChangeAnimLeftPercentage + } + } + } + + fun getCurrentViewPositionInFullPageSpace(): Float { + val rangeStart = lastPage.toFloat() + val rangeEndPage = if (abs(pageChangeSwipedPercentage) > 0f) { + if (pageChangeSwipedPercentage > 0f) { + currentPage - 1 + } else { + currentPage + 1 + } + } else { + currentPage + } + val rangeEnd = rangeEndPage.toFloat() + val lerpAmount = if (pageChangeAnimLeftPercentage > 0f) { + 1 - pageChangeAnimLeftPercentage + } else { + abs(pageChangeSwipedPercentage) + } + return MathUtils.lerp(rangeStart, rangeEnd, lerpAmount) + } + + fun setSwipeMovement(diffX: Float) { + if (!pageChangeEnabled) { + return + } + + if (currentPage < getMaxPage() && diffX > 0f || currentPage > 0 && diffX < 0f) { + pageChangeSwipedPercentage = (-diffX / getWidth().toFloat()).coerceIn(-1f, 1f) + pageChangeStarted() + redrawGrid() + } + } + + fun finalizeSwipe() { + if (abs(pageChangeSwipedPercentage) == 0f) { + return + } + + if (abs(pageChangeSwipedPercentage) > 0.5f) { + lastPage = currentPage + currentPage = if (pageChangeSwipedPercentage > 0f) { + currentPage - 1 + } else { + currentPage + 1 + } + handlePageChange(true) + } else { + lastPage = if (pageChangeSwipedPercentage > 0f) { + currentPage - 1 + } else { + currentPage + 1 + } + pageChangeSwipedPercentage = sign(pageChangeSwipedPercentage) * (1 - abs(pageChangeSwipedPercentage)) + handlePageChange(true) + } + } + + fun handleItemMovement(x: Int, y: Int) { + showIndicators() + if (x > getNextPageBound()) { + doWithPageChangeDelay(PageChangeArea.RIGHT) { + nextOrAdditionalPage() + } + } else if (x < getPrevPageBound()) { + doWithPageChangeDelay(PageChangeArea.LEFT) { + prevPage() + } + } else { + clearPageChangeFlags() + } + } + + fun itemMovementStopped() { + scheduleIndicatorsFade() + } + + fun nextPage(redraw: Boolean = false): Boolean { + if (currentPage < getMaxPage() && pageChangeEnabled) { + lastPage = currentPage + currentPage++ + handlePageChange(redraw) + return true + } + + return false + } + + fun prevPage(redraw: Boolean = false): Boolean { + if (currentPage > 0 && pageChangeEnabled) { + lastPage = currentPage + currentPage-- + handlePageChange(redraw) + return true + } + + return false + } + + fun skipToPage(targetPage: Int): Boolean { + if (currentPage != targetPage && targetPage < getMaxPage() + 1) { + lastPage = currentPage + currentPage = targetPage + handlePageChange() + return true + } + + return false + } + + private val checkAndExecuteDelayedPageChange: Runnable = Runnable { + if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) { + when (pageChangeLastArea) { + PageChangeArea.RIGHT -> nextOrAdditionalPage(true) + PageChangeArea.LEFT -> prevPage(true) + else -> clearPageChangeFlags() + } + } + } + + private val startFadingIndicators: Runnable = Runnable { + ValueAnimator.ofFloat(1f, 0f) + .apply { + addUpdateListener { + pageChangeIndicatorsAlpha = it.animatedValue as Float + redrawGrid() + } + start() + } + } + + private fun showIndicators() { + pageChangeIndicatorsAlpha = 1f + getHandler().removeCallbacks(startFadingIndicators) + } + + private fun clearPageChangeFlags() { + pageChangeLastArea = PageChangeArea.MIDDLE + pageChangeLastAreaEntryTime = 0 + getHandler().removeCallbacks(checkAndExecuteDelayedPageChange) + } + + private fun schedulePageChange() { + pageChangeLastAreaEntryTime = System.currentTimeMillis() + getHandler().postDelayed(checkAndExecuteDelayedPageChange, PAGE_CHANGE_HOLD_THRESHOLD) + } + + private fun scheduleIndicatorsFade() { + pageChangeIndicatorsAlpha = 1f + getHandler().postDelayed(startFadingIndicators, PAGE_INDICATORS_FADE_DELAY) + } + + private fun doWithPageChangeDelay(needed: PageChangeArea, pageChangeFunction: () -> Boolean) { + if (pageChangeLastArea != needed) { + pageChangeLastArea = needed + schedulePageChange() + } else if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) { + if (pageChangeFunction()) { + clearPageChangeFlags() + } + } + } + + private fun nextOrAdditionalPage(redraw: Boolean = false): Boolean { + if (currentPage < getMaxPage() + 1 && pageChangeEnabled) { + lastPage = currentPage + currentPage++ + handlePageChange(redraw) + return true + } + + return false + } + + private fun handlePageChange(redraw: Boolean = false) { + pageChangeEnabled = false + pageChangeIndicatorsAlpha = 0f + pageChangeStarted() + val startingAt = 1 - abs(pageChangeSwipedPercentage) + pageChangeSwipedPercentage = 0f + getHandler().removeCallbacks(startFadingIndicators) + if (redraw) { + redrawGrid() + } + ValueAnimator.ofFloat(startingAt, 0f) + .apply { + addUpdateListener { + pageChangeAnimLeftPercentage = it.animatedValue as Float + redrawGrid() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + pageChangeAnimLeftPercentage = 0f + pageChangeEnabled = true + lastPage = currentPage + clearPageChangeFlags() + scheduleIndicatorsFade() + redrawGrid() + } + }) + start() + } + } +} + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0274a14..c99b6cf 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -11,8 +11,8 @@ Gérer les icônes cachées Icônes cachées Certaines applications ne peuvent pas être désinstallées en raison de restrictions du système, mais vous pouvez au moins masquer leurs icônes pour éviter de les voir. - App drawer - Close app drawer on opening an app + Tiroir d\'appli + Fermer le tiroir de l\'appli à l\'ouverture d\'une application Home screen - Widget is too big for current home screen size - + Le widget est trop grand pour la taille actuelle de l\'écran + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index bde015f..450f88b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -11,8 +11,8 @@ Gestisci le icone nascoste Icone nascoste Alcune applicazioni non possono essere disinstallate a causa di restrizioni di sistema, ma è possibile almeno nascondere le loro icone per evitare di vederle. - App drawer - Close app drawer on opening an app + Cassetto app + Chiudi il cassetto app all\'apertura di un\'app Home screen - Widget is too big for current home screen size - + Il widget è troppo grande per le dimensioni attuali della schermata principale + \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 84b538c..1a1d2ad 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -12,7 +12,7 @@ Dolda ikoner Vissa appar kan inte avinstalleras på grund av systembegränsningar, men du kan åtminstone dölja deras ikoner för att slippa se dem. Applåda - Close app drawer on opening an app + Stäng applådan när en app öppnas Startskärm Widgeten är för stor för startskärmens aktuella storlek - + \ No newline at end of file