diff --git a/Omega/src/com/neoapps/neolauncher/allapps/search/ComposeSearchLayout.kt b/Omega/src/com/neoapps/neolauncher/allapps/search/ComposeSearchLayout.kt index 28d642c5e9..86a6977002 100644 --- a/Omega/src/com/neoapps/neolauncher/allapps/search/ComposeSearchLayout.kt +++ b/Omega/src/com/neoapps/neolauncher/allapps/search/ComposeSearchLayout.kt @@ -302,13 +302,16 @@ open class ComposeSearchLayout(context: Context, attrs: AttributeSet? = null) : mAppsView?.onClearSearchResult() } - fun startSearch() { + override fun startSearch() { startSearch("") } fun startSearch(searchQuery: String) { query.value = searchQuery.trim() - textFieldFocusRequester.requestFocus() + if (::textFieldFocusRequester.isInitialized) { + textFieldFocusRequester.requestFocus() + keyboardController?.show() + } } override fun getEditText(): ExtendedEditText? = null diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e63157e66a..f9017b926c 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -141,6 +141,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; @@ -1638,10 +1639,27 @@ private void toggleAllApps(boolean alreadyOnHome, boolean focusSearch) { new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { - if (focusSearch - && mAppsView.getSearchUiManager().getEditText() != null) { - mAppsView.getSearchUiManager().getEditText().requestFocus(); + if (!focusSearch) { + return; } + if (hasWindowFocus()) { + mAppsView.getSearchUiManager().startSearch(); + return; + } + ViewTreeObserver.OnWindowFocusChangeListener listener = + new ViewTreeObserver.OnWindowFocusChangeListener() { + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if (hasFocus) { + mAppsView.getSearchUiManager().startSearch(); + getWindow().getDecorView() + .getViewTreeObserver() + .removeOnWindowFocusChangeListener(this); + } + } + }; + getWindow().getDecorView().getViewTreeObserver() + .addOnWindowFocusChangeListener(listener); } }); } diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java index bfd89678df..44883e4873 100644 --- a/src/com/android/launcher3/allapps/SearchUiManager.java +++ b/src/com/android/launcher3/allapps/SearchUiManager.java @@ -48,6 +48,11 @@ public interface SearchUiManager { @Nullable ExtendedEditText getEditText(); + /** + * Requests focus on the search field and shows the keyboard if appropriate. + */ + default void startSearch() {} + /** * Hint to the edit text that it is about to be focused or unfocused. This can be used to start * animating the edit box accordingly, e.g. after a gesture completes. diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index a6c7d1e53e..2e5eded1cf 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -192,4 +192,9 @@ public void setInsets(Rect insets) { public ExtendedEditText getEditText() { return this; } + + @Override + public void startSearch() { + mSearchBarController.focusSearchField(); + } } diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java index bc7b7f00d2..8fb63e1aaf 100644 --- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java +++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java @@ -21,6 +21,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.util.FloatProperty; +import android.util.Log; import androidx.annotation.FloatRange; import androidx.dynamicanimation.animation.SpringForce; @@ -33,6 +34,8 @@ */ public class SpringAnimationBuilder { + private static final String TAG = "SpringAnimationBuilder"; + private final Context mContext; private float mStartValue; @@ -134,6 +137,26 @@ private float getValue(float time) { } public SpringAnimationBuilder computeParams() { + if (!Float.isFinite(mStartValue) || !Float.isFinite(mEndValue)) { + throw new IllegalArgumentException("Start/End values must be finite. start=" + + mStartValue + " end=" + mEndValue); + } + if (!Float.isFinite(mStiffness) || mStiffness <= 0) { + throw new IllegalArgumentException("Stiffness must be finite and positive: " + + mStiffness); + } + if (!Float.isFinite(mDampingRatio) || mDampingRatio <= 0 || mDampingRatio >= 1) { + throw new IllegalArgumentException("Damping ratio must be finite and in (0,1): " + + mDampingRatio); + } + if (!Float.isFinite(mMinVisibleChange) || mMinVisibleChange <= 0) { + throw new IllegalArgumentException("Minimum visible change must be finite and positive: " + + mMinVisibleChange); + } + if (!Float.isFinite(mVelocity)) { + throw new IllegalArgumentException("Start velocity must be finite: " + mVelocity); + } + int singleFrameMs = RefreshRateTracker.getSingleFrameMs(mContext); double naturalFreq = Math.sqrt(mStiffness); double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio); @@ -194,8 +217,29 @@ public ValueAnimator build(T target, FloatProperty property) { ValueAnimator animator = ValueAnimator.ofFloat(0, mDuration); animator.setDuration(getDuration()).setInterpolator(LINEAR); - animator.addUpdateListener(anim -> - property.set(target, getInterpolatedValue(anim.getAnimatedFraction()))); + animator.addUpdateListener(anim -> { + float fraction = anim.getAnimatedFraction(); + if (Float.isNaN(fraction)) { + // Skip updates before the animator has a valid fraction (can happen for + // zero-duration children inside an AnimatorSet when setCurrentPlayTime is called). + return; + } + float value = getInterpolatedValue(fraction); + if (!Float.isFinite(value)) { + Log.e(TAG, "Non-finite spring value for target=" + target.getClass().getName() + + " property=" + property.getName() + + " start=" + mStartValue + + " end=" + mEndValue + + " stiffness=" + mStiffness + + " damping=" + mDampingRatio + + " minVisibleChange=" + mMinVisibleChange + + " velocity=" + mVelocity + + " fraction=" + fraction + + " value=" + value); + value = mEndValue; + } + property.set(target, value); + }); animator.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animation) { diff --git a/src/com/android/launcher3/folder/ClipRevealData.kt b/src/com/android/launcher3/folder/ClipRevealData.kt index a7448f5e54..567cf8cd5d 100644 --- a/src/com/android/launcher3/folder/ClipRevealData.kt +++ b/src/com/android/launcher3/folder/ClipRevealData.kt @@ -72,18 +72,21 @@ data class ClipRevealData( val pageStart = page * layoutParams.width // Setup start and end area for revealing Folder Content + val safeInitialFolderScale = if (initialFolderScale > 0f && !initialFolderScale.isNaN()) { + initialFolderScale + } else 1f val extraRadius = - ((deviceProfile.folderIconSizePx / initialFolderScale) * + ((deviceProfile.folderIconSizePx / safeInitialFolderScale) * EXTRA_FOLDER_REVEAL_RADIUS_PERCENTAGE) .toInt() val contentStart = Rect( - (pageStart + (backgroundStartRect.left / initialFolderScale)).toInt() - + (pageStart + (backgroundStartRect.left / safeInitialFolderScale)).toInt() - extraRadius, - (backgroundStartRect.top / initialFolderScale).toInt() - extraRadius, - (pageStart + (backgroundStartRect.right / initialFolderScale)).toInt() + + (backgroundStartRect.top / safeInitialFolderScale).toInt() - extraRadius, + (pageStart + (backgroundStartRect.right / safeInitialFolderScale)).toInt() + extraRadius, - (backgroundStartRect.bottom / initialFolderScale).toInt() + extraRadius, + (backgroundStartRect.bottom / safeInitialFolderScale).toInt() + extraRadius, ) val contentEnd = Rect(pageStart, 0, pageStart + layoutParams.width, layoutParams.height) diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 439ae7ef56..d9b862c2c9 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -827,8 +827,24 @@ private void animateOpen(List items, int pageNo) { Log.d("b/383526431", "animateOpen: content child count after cancelling" + " animation: " + mContent.getTotalChildCount()); - AnimatorSet animatorSet = getFolderAnimationManager() - .createAnimatorSet(/* isOpening */ true); + AnimatorSet animatorSet; + try { + animatorSet = getFolderAnimationManager() + .createAnimatorSet(/* isOpening */ true); + } catch (Exception e) { + Log.e(TAG, "Failed to create folder open animator, opening without animation", e); + mFolderIcon.setIconVisible(false); + mFolderIcon.drawLeaveBehindIfExists(); + setState(STATE_OPEN); + announceAccessibilityChanges(); + AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(), + FOLDER_OPENED_MESSAGE); + mContent.setFocusOnFirstChild(); + if (mPageIndicator != null) { + mPageIndicator.stopAllAnimations(); + } + return; + } animatorSet.addListener(new AnimatorListenerAdapter() { @Override @@ -896,12 +912,26 @@ public void onAnimationEnd(Animator animation) { // {@link AnimatorListener} before it so that {@link AnimatorListener#onAnimationStart} can // be called to register mCurrentAnimator, which will be used to cancel animator addAnimationStartListeners(animatorSet); - // Because t=0 has the folder match the folder icon, we can skip the - // first frame and have the same movement one frame earlier. - Log.d("b/311077782", "Folder.animateOpen"); - animatorSet.setCurrentPlayTime(Math.min( - getSingleFrameMs(getContext()), animatorSet.getTotalDuration())); - animatorSet.start(); + try { + // Because t=0 has the folder match the folder icon, we can skip the + // first frame and have the same movement one frame earlier. + Log.d("b/311077782", "Folder.animateOpen"); + animatorSet.setCurrentPlayTime(Math.min( + getSingleFrameMs(getContext()), animatorSet.getTotalDuration())); + animatorSet.start(); + } catch (Exception e) { + Log.e(TAG, "Failed to start folder open animation, opening without animation", e); + mFolderIcon.setIconVisible(false); + mFolderIcon.drawLeaveBehindIfExists(); + setState(STATE_OPEN); + announceAccessibilityChanges(); + AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(), + FOLDER_OPENED_MESSAGE); + mContent.setFocusOnFirstChild(); + if (mPageIndicator != null) { + mPageIndicator.stopAllAnimations(); + } + } // Make sure the folder picks up the last drag move even if the finger doesn't move. diff --git a/src/com/android/launcher3/folder/FolderAnimationData.kt b/src/com/android/launcher3/folder/FolderAnimationData.kt index 72962e8197..bc1a14d08a 100644 --- a/src/com/android/launcher3/folder/FolderAnimationData.kt +++ b/src/com/android/launcher3/folder/FolderAnimationData.kt @@ -75,8 +75,33 @@ data class FolderAnimationData( ) val scaledFolderRadius: Int = previewBackground.scaledRadius val baseIconSize: Float = getBubbleTextView(itemsInPreview[0]).iconSize.toFloat() - val initialFolderSize = (scaledFolderRadius * 2) * scaleRelativeToDragLayer - val initialFolderScale = previewSize / baseIconSize * scaleRelativeToDragLayer + // Guard against unlaid-out icons (can happen when an overlay window triggers + // a premature layout), which would otherwise produce NaN/Inf scale values. + val safeScaleRelativeToDragLayer = + if (scaleRelativeToDragLayer > 0f && !scaleRelativeToDragLayer.isNaN()) { + scaleRelativeToDragLayer + } else 1f + val safeBaseIconSize = when { + baseIconSize > 0f && !baseIconSize.isNaN() -> baseIconSize + previewSize > 0f && !previewSize.isNaN() -> previewSize + else -> 1f + } + val safePreviewSize = when { + previewSize > 0f && !previewSize.isNaN() -> previewSize + safeBaseIconSize > 0f -> safeBaseIconSize + else -> 1f + } + val initialFolderSize = (scaledFolderRadius * 2) * safeScaleRelativeToDragLayer + val initialFolderScale = + (safePreviewSize / safeBaseIconSize * safeScaleRelativeToDragLayer).let { + if (it > 0f && !it.isNaN()) it else 1f + } + android.util.Log.e( + "FolderAnimationData", + "baseIconSize=$baseIconSize previewSize=$previewSize scaleRelativeToDragLayer=$scaleRelativeToDragLayer " + + "safeScale=$safeScaleRelativeToDragLayer safeBase=$safeBaseIconSize safePreview=$safePreviewSize " + + "initialFolderScale=$initialFolderScale" + ) // Get offsets for Previews and Content val initialPreviewItemOffsetX = @@ -90,13 +115,13 @@ data class FolderAnimationData( val initialX = ((folderIconWorkspacePosition.left + paddingLeft + - Math.round(previewBackground.offsetX * scaleRelativeToDragLayer)) - + Math.round(previewBackground.offsetX * safeScaleRelativeToDragLayer)) - contentOffsetX - initialPreviewItemOffsetX) val initialY = ((folderIconWorkspacePosition.top + paddingTop + - Math.round(previewBackground.offsetY * scaleRelativeToDragLayer)) - + Math.round(previewBackground.offsetY * safeScaleRelativeToDragLayer)) - contentOffsetY) // Get scaled height of content and radius of background @@ -110,7 +135,7 @@ data class FolderAnimationData( return FolderAnimationData( isOpening = isOpening, startScale = if (isOpening) initialFolderScale else 1f, - folderScale = initialFolderScale / scaleRelativeToDragLayer, + folderScale = initialFolderScale / safeScaleRelativeToDragLayer, xDistance = (initialX - layoutParams.x).toFloat(), yDistance = (initialY - layoutParams.y).toFloat(), contentHeightDifference = contentHeightDifference, @@ -119,7 +144,7 @@ data class FolderAnimationData( initialFolderSize = initialFolderSize, previewOffsetX = initialPreviewItemOffsetX + contentOffsetX, scaledPreviewOffsetX = - (initialPreviewItemOffsetX / scaleRelativeToDragLayer).toInt() + + (initialPreviewItemOffsetX / safeScaleRelativeToDragLayer).toInt() + folderRadiusDifference, contentOffsetY = contentOffsetY, defaultDuration = diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 3567e0f1b1..cc6e98c16e 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -149,15 +149,26 @@ public AnimatorSet createAnimatorSet(boolean isOpening) { float scaleRelativeToDragLayer = mFolder.mActivityContext.getDragLayer() .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos); int scaledRadius = mPreviewBackground.getScaledRadius(); - float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer; // Match size/scale of icons in the preview float previewScale = rule.scaleForItem(itemsInPreview.size(), 0); float previewSize = rule.getIconSize() * previewScale; float baseIconSize = getBubbleTextView(itemsInPreview.get(0)).getIconSize(); - float initialScale = previewSize / baseIconSize * scaleRelativeToDragLayer; + // Guard against unlaid-out icons (can happen when an overlay window triggers + // a premature layout), which would otherwise produce NaN/Inf scale values. + float safeScaleRelativeToDragLayer = (scaleRelativeToDragLayer > 0f && !Float.isNaN(scaleRelativeToDragLayer)) + ? scaleRelativeToDragLayer : 1f; + float safeBaseIconSize = (baseIconSize > 0f && !Float.isNaN(baseIconSize)) + ? baseIconSize : ((previewSize > 0f && !Float.isNaN(previewSize)) ? previewSize : 1f); + float safePreviewSize = (previewSize > 0f && !Float.isNaN(previewSize)) + ? previewSize : safeBaseIconSize; + float initialScale = safePreviewSize / safeBaseIconSize * safeScaleRelativeToDragLayer; + if (initialScale <= 0f || Float.isNaN(initialScale)) { + initialScale = 1f; + } + float initialSize = (scaledRadius * 2) * safeScaleRelativeToDragLayer; final float finalScale = 1f; - float scale = mIsOpening ? initialScale : finalScale; + float scale = sanitizeFloat(mIsOpening ? initialScale : finalScale); mFolder.setPivotX(0); mFolder.setPivotY(0); @@ -180,10 +191,10 @@ public AnimatorSet createAnimatorSet(boolean isOpening) { final int paddingOffsetY = (int) (mContent.getPaddingTop() * initialScale); int initialX = folderIconPos.left + mFolder.getPaddingLeft() - + Math.round(mPreviewBackground.getOffsetX() * scaleRelativeToDragLayer) + + Math.round(mPreviewBackground.getOffsetX() * safeScaleRelativeToDragLayer) - paddingOffsetX - previewItemOffsetX; int initialY = folderIconPos.top + mFolder.getPaddingTop() - + Math.round(mPreviewBackground.getOffsetY() * scaleRelativeToDragLayer) + + Math.round(mPreviewBackground.getOffsetY() * safeScaleRelativeToDragLayer) - paddingOffsetY; final float xDistance = initialX - lp.x; final float yDistance = initialY - lp.y; @@ -352,10 +363,10 @@ public void onAnimationEnd(Animator animation) { } int radiusDiff = scaledRadius - mPreviewBackground.getRadius(); - addPreviewItemAnimators(a, initialScale / scaleRelativeToDragLayer, + addPreviewItemAnimators(a, sanitizeFloat(initialScale / safeScaleRelativeToDragLayer), // Background can have a scaled radius in drag and drop mode, so we need to add the // difference to keep the preview items centered. - (int) (previewItemOffsetX / scaleRelativeToDragLayer) + radiusDiff, radiusDiff); + (int) (previewItemOffsetX / safeScaleRelativeToDragLayer) + radiusDiff, radiusDiff); return a; } @@ -517,4 +528,8 @@ private BubbleTextView getBubbleTextView(View v) { ? ((AppPairIcon) v).getTitleTextView() : (BubbleTextView) v; } + + private static float sanitizeFloat(float value) { + return (value > 0f && !Float.isNaN(value) && value != Float.POSITIVE_INFINITY && value != Float.NEGATIVE_INFINITY) ? value : 1f; + } } diff --git a/src/com/android/launcher3/folder/FolderSpringAnimatorSet.kt b/src/com/android/launcher3/folder/FolderSpringAnimatorSet.kt index 158152cba7..3d8fa11653 100644 --- a/src/com/android/launcher3/folder/FolderSpringAnimatorSet.kt +++ b/src/com/android/launcher3/folder/FolderSpringAnimatorSet.kt @@ -104,6 +104,14 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { .setEndValue(if (isOpening) endValue else startValue) .setMinimumVisibleChange(minVisibleChange) + if (!startValue.isFinite() || !endValue.isFinite()) { + android.util.Log.e( + "FolderSpringAnimatorSet", + "playSpringAnimation non-finite value for view=$view " + + "property=${property.name} startValue=$startValue endValue=$endValue" + ) + } + val animator = animatorBuilder.build(view, property as FloatProperty).apply { setStartDelay(startDelay.toLong()) @@ -117,15 +125,20 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { pivotX = 0f pivotY = 0f } + val startScale = folderAnimationData.startScale.sanitize() + android.util.Log.e( + "FolderSpringAnimatorSet", + "setupFolder startScaleRaw=${folderAnimationData.startScale} startScaleSafe=$startScale" + ) folder.content.apply { - scaleX = folderAnimationData.startScale - scaleY = folderAnimationData.startScale + scaleX = startScale + scaleY = startScale pivotX = 0f pivotY = 0f } folder.mFooter.apply { - scaleX = folderAnimationData.startScale - scaleY = folderAnimationData.startScale + scaleX = startScale + scaleY = startScale pivotX = 0f pivotY = 0f } @@ -163,6 +176,11 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { property = View.TRANSLATION_Y, view = folder, ) + val sanitizedInitialFolderScale = animationData.initialFolderScale.sanitize() + android.util.Log.e( + "FolderSpringAnimatorSet", + "addFolderScale initialFolderScaleRaw=${animationData.initialFolderScale} safe=$sanitizedInitialFolderScale" + ) playSpringAnimation( context = folder.context, animatorSet = animatorSet, @@ -170,7 +188,7 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { startDelay = 0, stiffness = STIFFNESS_SHAPE_POSITION, damping = DAMPING_SHAPE_POSITION, - startValue = animationData.initialFolderScale, + startValue = sanitizedInitialFolderScale, endValue = 1f, minVisibleChange = MIN_VISIBLE_CHANGE_SCALE, property = SCALE_PROPERTY, @@ -183,7 +201,7 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { startDelay = 0, stiffness = STIFFNESS_SHAPE_POSITION, damping = DAMPING_SHAPE_POSITION, - startValue = animationData.initialFolderScale, + startValue = sanitizedInitialFolderScale, endValue = 1f, minVisibleChange = MIN_VISIBLE_CHANGE_SCALE, property = SCALE_PROPERTY, @@ -440,6 +458,10 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { property = View.TRANSLATION_Y, view = icon, ) + android.util.Log.e( + "FolderSpringAnimatorSet", + "iconScale icon=$icon initialIconScale=$initialIconScale" + ) playSpringAnimation( context = context, animatorSet = animatorSet, @@ -500,5 +522,8 @@ class FolderSpringAnimatorSet(val animatorSet: AnimatorSet) { } } } + + private fun Float.sanitize(): Float = + if (this > 0f && !this.isNaN() && this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY) this else 1f } } diff --git a/src/com/android/launcher3/folder/IconAnimationData.kt b/src/com/android/launcher3/folder/IconAnimationData.kt index e1a37a0f3c..ade7b5b2d6 100644 --- a/src/com/android/launcher3/folder/IconAnimationData.kt +++ b/src/com/android/launcher3/folder/IconAnimationData.kt @@ -77,10 +77,23 @@ data class IconAnimationData( val previewIconScale = layoutRule.scaleForItem(numItemsOnPage, page) val previewIconSize = layoutRule.iconSize * previewIconScale val baseIconSize = getBubbleTextView(currentIcon).iconSize.toFloat() - val iconScale = previewIconSize / baseIconSize + // Guard against unlaid-out icons producing NaN scale values. + val safeBaseIconSize = if (baseIconSize > 0f && !baseIconSize.isNaN()) baseIconSize else 1f + val safePreviewIconSize = if (previewIconSize > 0f && !previewIconSize.isNaN()) previewIconSize else safeBaseIconSize + val iconScale = safePreviewIconSize / safeBaseIconSize // Scale when folder closed - val initialIconScale = iconScale / folderAnimationData.folderScale + val safeFolderScale = if (folderAnimationData.folderScale > 0f && !folderAnimationData.folderScale.isNaN()) { + folderAnimationData.folderScale + } else 1f + val initialIconScale = (iconScale / safeFolderScale).let { + if (it > 0f && !it.isNaN()) it else 1f + } + android.util.Log.e( + "IconAnimationData", + "icon=$currentIcon baseIconSize=$baseIconSize previewIconSize=$previewIconSize " + + "iconScale=$iconScale safeFolderScale=$safeFolderScale initialIconScale=$initialIconScale" + ) // Scale when folder open val finalIconScale = 1f // Scale to start with in Animation