Commit 10dac382 authored by Amit Kumar's avatar Amit Kumar 💻
Browse files

Separate folder logic from launcher activity

parent 4bf4103d
Pipeline #151609 passed with stage
in 8 minutes and 20 seconds
......@@ -85,7 +85,7 @@ class ShaderBlurDrawable internal constructor(private val blurWallpaperProvider:
if (canvasOffset > 0)
canvas.translate(-canvasOffset, 0f)
}
override fun setAlpha(alpha: Int) {
blurAlpha = alpha
blurPaint.alpha = alpha
......
......@@ -62,9 +62,7 @@ class FolderTitleInput @JvmOverloads constructor(
fun dispatchBackKey() {
hideKeyboard()
if (mBackKeyListener != null) {
mBackKeyListener!!.onBackKey()
}
mBackKeyListener?.onBackKey()
}
/**
......
......@@ -38,9 +38,6 @@ import android.view.animation.AnimationUtils;
import android.view.animation.OvershootInterpolator;
import android.widget.GridLayout;
import android.widget.Toast;
import androidx.viewpager.widget.ViewPager;
import foundation.e.blisslauncher.BuildConfig;
import foundation.e.blisslauncher.R;
import foundation.e.blisslauncher.core.Utilities;
......@@ -57,6 +54,7 @@ import foundation.e.blisslauncher.core.utils.GraphicsUtil;
import foundation.e.blisslauncher.core.utils.IntSparseArrayMap;
import foundation.e.blisslauncher.core.utils.IntegerArray;
import foundation.e.blisslauncher.core.utils.PackageUserKey;
import foundation.e.blisslauncher.features.folder.FolderIcon;
import foundation.e.blisslauncher.features.launcher.Hotseat;
import foundation.e.blisslauncher.features.notification.FolderDotInfo;
import foundation.e.blisslauncher.features.shortcuts.DeepShortcutManager;
......@@ -64,7 +62,6 @@ import foundation.e.blisslauncher.features.shortcuts.InstallShortcutReceiver;
import foundation.e.blisslauncher.features.shortcuts.ShortcutKey;
import foundation.e.blisslauncher.features.test.Alarm;
import foundation.e.blisslauncher.features.test.CellLayout;
import foundation.e.blisslauncher.features.test.FolderIconTextView;
import foundation.e.blisslauncher.features.test.IconTextView;
import foundation.e.blisslauncher.features.test.LauncherItemMatcher;
import foundation.e.blisslauncher.features.test.LauncherState;
......@@ -84,7 +81,6 @@ import foundation.e.blisslauncher.features.test.dragndrop.DropTarget;
import foundation.e.blisslauncher.features.test.dragndrop.SpringLoadedDragController;
import foundation.e.blisslauncher.features.test.graphics.DragPreviewProvider;
import foundation.e.blisslauncher.features.test.uninstall.UninstallHelper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
......@@ -94,7 +90,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
public class LauncherPagedView extends PagedView<PageIndicatorDots> implements View.OnTouchListener,
......@@ -338,11 +333,8 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
LauncherItem launcherItem = launcherItems.get(i);
View appView;
if (launcherItem.itemType == Constants.ITEM_TYPE_FOLDER) {
FolderIconTextView folderIcon =
(FolderIconTextView) LayoutInflater.from(getContext())
.inflate(R.layout.folder_icon, null, false);
FolderIcon folderIcon = FolderIcon.Companion.fromXml(R.layout.folder_icon, getScreenWithId(launcherItem.screenId), (FolderItem) launcherItem);
folderIcon.applyFromFolderItem((FolderItem) launcherItem);
((FolderItem) launcherItem).addListener(folderIcon);
appView = folderIcon;
} else {
IconTextView appIcon = (IconTextView) LayoutInflater.from(getContext())
......@@ -537,8 +529,8 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
*/
public void removeFolderListeners() {
mapOverItems(false, (info, view, index) -> {
if (view instanceof FolderIconTextView) {
((FolderIconTextView) view).removeListeners();
if (view instanceof FolderIcon) {
((FolderIcon) view).removeListeners();
}
return false;
});
......@@ -1532,6 +1524,38 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
mDragInfo = null;
}
/**
* Unbinds the view for the specified item, and removes the item and all its children.
*
* @param v the view being removed.
* @param itemInfo the {@link LauncherItem} for this view.
*/
public boolean removeItem(View v, final LauncherItem itemInfo) {
if (itemInfo instanceof ApplicationItem || itemInfo instanceof ShortcutItem) {
// Remove the shortcut from the folder before removing it from launcher
View folderIcon = getHomescreenIconByItemId(String.valueOf(itemInfo.container));
if (folderIcon instanceof FolderIcon) {
((FolderItem) folderIcon.getTag()).remove(itemInfo, true);
} else {
removeWorkspaceItem(v);
}
updateDatabase();
} else if (itemInfo instanceof FolderItem) {
if (v instanceof FolderIcon) {
((FolderIcon) v).removeListeners();
}
removeWorkspaceItem(v);
updateDatabase();
} else {
return false;
}
return true;
}
public View getHomescreenIconByItemId(final String id) {
return getFirstMatch((info, v, idx) -> info != null && info.id == id);
}
/**
* For opposite operation. See {@link #addInScreen}.
*/
......@@ -1898,7 +1922,7 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
fi.screenId = screenId;
fi.cell = targetCell[1] * mLauncher.getDeviceProfile().getInv()
.getNumColumns() + targetCell[0];
FolderIconTextView folderView = (FolderIconTextView) LayoutInflater.from(getContext())
FolderIcon folderView = (FolderIcon) LayoutInflater.from(getContext())
.inflate(R.layout.folder_icon, null, false);
folderView.applyFromShortcutItem(fi);
folderView.setOnClickListener(ItemClickHandler.INSTANCE);
......@@ -2567,6 +2591,18 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
);
}
public View getFirstMatch(final ItemOperator operator) {
final View[] value = new View[1];
mapOverItems(MAP_NO_RECURSE, (info, v, index) -> {
if (operator.evaluate(info, v, index)) {
value[0] = v;
return true;
}
return false;
});
return value[0];
}
/**
* @param cellLayouts List of CellLayouts to scan, in order of preference.
* @param operators List of operators, in order starting from best matching operator.
......@@ -2663,12 +2699,12 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
// Update folder icons
mapOverItems(MAP_NO_RECURSE, (info, v, itemIdx) -> {
if (info instanceof FolderItem && folderIds.contains(info.id)
&& v instanceof FolderIconTextView) {
&& v instanceof FolderIcon) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
for (LauncherItem si : ((FolderItem) info).items) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
}
((FolderIconTextView) v).setDotInfo(folderDotInfo);
((FolderIcon) v).setDotInfo(folderDotInfo);
}
// process all the shortcuts
return false;
......@@ -2831,7 +2867,7 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
FolderItem folder = (FolderItem) parent.getTag();
parent.clearAnimation();
// Close folder before making any changes
mLauncher.closeFolder();
// mLauncher.closeFolder();
folder.items.remove(itemToRemove);
DatabaseManager.getManager(getContext()).removeItem(itemToRemove.id);
if (folder.items.size() == 0) {
......
package foundation.e.blisslauncher.core.database.model;
import org.jetbrains.annotations.NotNull;
import foundation.e.blisslauncher.core.utils.Constants;
import java.util.ArrayList;
......@@ -34,7 +36,24 @@ public class FolderItem extends LauncherItem {
listeners.remove(listener);
}
public void remove(@NotNull LauncherItem launcherItem, boolean animate) {
items.remove(launcherItem);
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onRemove(launcherItem);
}
itemsChanged(animate);
}
public void itemsChanged(boolean animate) {
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onItemsChanged(animate);
}
}
public interface FolderListener {
void onAdd(LauncherItem item);
void onTitleChanged(CharSequence title);
void onRemove(LauncherItem item);
void onItemsChanged(boolean animate);
}
}
......@@ -21,10 +21,12 @@ import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import foundation.e.blisslauncher.core.customviews.Folder;
import foundation.e.blisslauncher.core.database.model.ApplicationItem;
import foundation.e.blisslauncher.core.database.model.FolderItem;
import foundation.e.blisslauncher.core.database.model.LauncherItem;
import foundation.e.blisslauncher.core.database.model.ShortcutItem;
import foundation.e.blisslauncher.features.folder.FolderIcon;
import foundation.e.blisslauncher.features.test.IconTextView;
import foundation.e.blisslauncher.features.test.TestActivity;
......@@ -60,7 +62,7 @@ public class ItemClickHandler {
if (tag instanceof ShortcutItem) {
onClickAppShortcut(v, (ShortcutItem) tag, launcher);
} else if (tag instanceof FolderItem) {
onClickFolderIcon(v, launcher);
onClickFolderIcon(v);
} else if (tag instanceof ApplicationItem) {
startAppShortcutOrInfoActivity(v, (ApplicationItem) tag, launcher);
}
......@@ -70,14 +72,13 @@ public class ItemClickHandler {
* Event handler for a folder icon click.
*
* @param v The view that was clicked. Must be an instance of {@link IconTextView}.
* @param launcher Launcher activity to pass actions.
*/
private static void onClickFolderIcon(
View v,
TestActivity launcher
) {
Log.d("ItemClick", "onClickFolderIcon() called with: v = [" + v + "]");
launcher.openFolder(v);
private static void onClickFolderIcon(View v) {
Folder folder = ((FolderIcon) v).getFolder();
if (!folder.isOpen() && !folder.isDestroyed()) {
// Open the requested folder
folder.animateOpen();
}
}
/**
......
......@@ -20,6 +20,7 @@ import static foundation.e.blisslauncher.features.test.LauncherState.OVERVIEW;
import android.view.View;
import android.view.View.OnLongClickListener;
import foundation.e.blisslauncher.core.customviews.Folder;
import foundation.e.blisslauncher.core.database.model.LauncherItem;
import foundation.e.blisslauncher.features.test.CellLayout;
import foundation.e.blisslauncher.features.test.TestActivity;
......@@ -58,7 +59,7 @@ public class ItemLongClickListener {
DragOptions dragOptions
) {
if (info.container >= 0) {
Folder folder = Folder.getOpen(launcher);
Folder folder = Folder.Companion.getOpen(launcher);
if (folder != null) {
if (!folder.getItemsInReadingOrder().contains(v)) {
folder.close(true);
......
/*
* Copyright (C) 2017 The Android Open Source 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 foundation.e.blisslauncher.features.folder;
import static foundation.e.blisslauncher.features.test.anim.LauncherAnimUtils.SCALE_PROPERTY;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.util.Property;
import android.view.View;
import android.view.animation.AnimationUtils;
import androidx.viewpager.widget.ViewPager;
import foundation.e.blisslauncher.R;
import foundation.e.blisslauncher.core.customviews.Folder;
import foundation.e.blisslauncher.features.test.TestActivity;
import foundation.e.blisslauncher.features.test.dragndrop.DragLayer;
/**
* Manages the opening and closing animations for a {@link Folder}.
* <p>
* All of the animations are done in the Folder.
* ie. When the user taps on the FolderIcon, we immediately hide the FolderIcon and show the Folder
* in its place before starting the animation.
*/
public class FolderAnimationManager {
private Folder mFolder;
private ViewPager mContent;
private GradientDrawable mFolderBackground;
private FolderIcon mFolderIcon;
private Context mContext;
private TestActivity mLauncher;
private final boolean mIsOpening;
private final int mDuration;
private final int mDelay;
private final TimeInterpolator mFolderInterpolator;
public FolderAnimationManager(Folder folder, boolean isOpening) {
mFolder = folder;
mContent = folder.mContent;
mFolderIcon = folder.getFolderIcon();
mContext = folder.getContext();
mLauncher = folder.getLauncher();
mIsOpening = isOpening;
Resources res = mContent.getResources();
mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
mDelay = res.getInteger(R.integer.config_folderDelay);
mFolderInterpolator = AnimationUtils.loadInterpolator(
mContext,
R.anim.folder_interpolator
);
}
/**
* Prepares the Folder for animating between open / closed states.
*/
public AnimatorSet getAnimator() {
final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
float scaleRelativeToDragLayer = mLauncher.getDragLayer()
.getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
// Calculate the starting and ending bounds for the zoomed-in image.
// This step involves lots of math. Yay, math.
Rect dragLayerBounds = new Rect();
Point globalOffset = new Point();
mLauncher.getDragLayer()
.getGlobalVisibleRect(dragLayerBounds, globalOffset);
float startScale = ((float) folderIconPos.width() / dragLayerBounds.width());
float startHeight = startScale * dragLayerBounds.height();
float deltaHeight = (startHeight - folderIconPos.height()) / 2;
folderIconPos.top -= deltaHeight;
folderIconPos.bottom += deltaHeight;
float initialScale = (float) folderIconPos.height() / dragLayerBounds.height();
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
float initialAlpha = 0f;
float finalAlpha = 1f;
mFolder.setScaleX(scale);
mFolder.setScaleY(scale);
mFolder.setPivotX(0);
mFolder.setPivotY(0);
// Create the animators.
AnimatorSet a = new AnimatorSet();
play(
a,
getAnimator(mFolder,
View.X,
folderIconPos.left,
dragLayerBounds.left
)
);
play(a, getAnimator(mFolder, View.Y, folderIconPos.top, dragLayerBounds.top));
play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
play(a, getAnimator(mFolder, View.ALPHA, initialAlpha, finalAlpha));
// play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
// Animate the elevation midway so that the shadow is not noticeable in the background.
int midDuration = mDuration / 2;
Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
play(a, z, mIsOpening ? midDuration : 0, midDuration);
a.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mFolder.setVisibility(View.VISIBLE);
mFolder.setPivotX(0f);
mFolder.setPivotY(0f);
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
if (mIsOpening) {
mFolder.setVisibility(View.GONE);
}
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mFolder.setScaleX(1f);
mFolder.setScaleY(1f);
if (!mIsOpening) {
mFolder.setVisibility(View.GONE);
}
}
});
return a;
}
private void play(AnimatorSet as, Animator a) {
play(as, a, a.getStartDelay(), mDuration);
}
private void play(AnimatorSet as, Animator a, long startDelay, int duration) {
a.setStartDelay(startDelay);
a.setDuration(duration);
as.play(a);
}
private TimeInterpolator getPreviewItemInterpolator() {
return mFolderInterpolator;
}
private Animator getAnimator(View view, Property property, float v1, float v2) {
return mIsOpening
? ObjectAnimator.ofFloat(view, property, v1, v2)
: ObjectAnimator.ofFloat(view, property, v2, v1);
}
private Animator getAnimator(GradientDrawable drawable, String property, int v1, int v2) {
return mIsOpening
? ObjectAnimator.ofArgb(drawable, property, v1, v2)
: ObjectAnimator.ofArgb(drawable, property, v2, v1);
}
}
package foundation.e.blisslauncher.features.test
package foundation.e.blisslauncher.features.folder
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import foundation.e.blisslauncher.core.customviews.Folder
import foundation.e.blisslauncher.core.database.model.FolderItem
import foundation.e.blisslauncher.core.database.model.LauncherItem
import foundation.e.blisslauncher.core.utils.GraphicsUtil
import foundation.e.blisslauncher.features.notification.FolderDotInfo
import foundation.e.blisslauncher.features.test.IconTextView
/**
* A text view which displays an icon on top side of it.
*/
@SuppressLint("AppCompatCustomView")
class FolderIconTextView @JvmOverloads constructor(
class FolderIcon @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : IconTextView(context, attrs, defStyle), FolderItem.FolderListener {
private var folderItem: FolderItem? = null
private lateinit var folderItem: FolderItem
var folder: Folder? = null
companion object {
fun fromXml(
resId: Int,
group: ViewGroup,
folderInfo: FolderItem
): FolderIcon {
val icon = LayoutInflater.from(group.context)
.inflate(resId, group, false) as FolderIcon
icon.tag = folderInfo
icon.folderItem = folderInfo
val folder = Folder.fromXml(icon.launcher).apply {
this.dragController = launcher.dragController
this.folderIcon = icon
}
folder.bind(folderInfo)
icon.folder = folder
folderInfo.addListener(icon)
return icon
}
}
override fun onAdd(item: LauncherItem?) {
if (mDotInfo is FolderDotInfo) {
(mDotInfo as FolderDotInfo).let {
val wasDotted: Boolean = it.hasDot()
it.addDotInfo(launcher.getDotInfoForItem(item))
val isDotted: Boolean = it.hasDot()
updateDotScale(wasDotted, isDotted, true)
invalidate()
requestLayout()
}
}
}
override fun onTitleChanged(title: CharSequence?) {
text = title
}
override fun onRemove(item: LauncherItem?) {
if (mDotInfo is FolderDotInfo) {
(mDotInfo as FolderDotInfo).let {
val wasDotted: Boolean = it.hasDot()
it.subtractDotInfo(launcher.getDotInfoForItem(item))
val isDotted: Boolean = it.hasDot()
updateDotScale(wasDotted, isDotted, true)
invalidate()
requestLayout()
}
}
}
override fun onItemsChanged(animate: Boolean) {
updateFolderIcon()
invalidate()
requestLayout()
}
private fun updateFolderIcon() {
folderItem.icon = GraphicsUtil(context).generateFolderIcon(context, folderItem)
applyFromFolderItem(folderItem)
}
fun applyFromFolderItem(item: FolderItem) {
applyFromShortcutItem(item)
folderItem = item
......@@ -38,6 +103,6 @@ class FolderIconTextView @JvmOverloads constructor(
}
fun removeListeners() {
folderItem?.removeListener(this)
folderItem.removeListener(this)
}
}