Commit 4bf4103d authored by Amit Kumar's avatar Amit Kumar 💻
Browse files

Add custom folder container to enhance folder UX and remove bugs

parent f8b1d573
Pipeline #151571 failed with stage
in 4 minutes and 8 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
......
......@@ -40,6 +40,7 @@ import foundation.e.blisslauncher.features.test.TouchController;
public abstract class AbstractFloatingView extends LinearLayout implements TouchController {
@IntDef(flag = true, value = {
TYPE_FOLDER,
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP,
TYPE_LISTENER
......@@ -48,13 +49,15 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
public @interface FloatingViewType {
}
public static final int TYPE_FOLDER = 1 << 0;
public static final int TYPE_LISTENER = 1 << 1;
// Popups related to quickstep UI
public static final int TYPE_TASK_MENU = 1 << 2;
public static final int TYPE_OPTIONS_POPUP = 1 << 3;
public static final int TYPE_ALL = TYPE_TASK_MENU | TYPE_OPTIONS_POPUP | TYPE_LISTENER;
public static final int TYPE_ALL =
TYPE_FOLDER | TYPE_TASK_MENU | TYPE_OPTIONS_POPUP | TYPE_LISTENER;
public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_LISTENER;
......
package foundation.e.blisslauncher.core.customviews
import android.animation.AnimatorSet
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.text.InputType
import android.text.Selection
import android.util.AttributeSet
import android.view.FocusFinder
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewDebug.ExportedProperty
import android.view.accessibility.AccessibilityEvent
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
import androidx.viewpager.widget.ViewPager
import foundation.e.blisslauncher.R
import foundation.e.blisslauncher.core.DeviceProfile
import foundation.e.blisslauncher.core.database.model.FolderItem
import foundation.e.blisslauncher.core.database.model.LauncherItem
import foundation.e.blisslauncher.features.folder.FolderPagerAdapter
import foundation.e.blisslauncher.features.test.Alarm
import foundation.e.blisslauncher.features.test.IconTextView
import foundation.e.blisslauncher.features.test.OnAlarmListener
import foundation.e.blisslauncher.features.test.TestActivity
import foundation.e.blisslauncher.features.test.VariantDeviceProfile
import foundation.e.blisslauncher.features.test.dragndrop.DragController
import foundation.e.blisslauncher.features.test.dragndrop.DragLayer
import foundation.e.blisslauncher.features.test.dragndrop.DragOptions
import foundation.e.blisslauncher.features.test.dragndrop.DragSource
import foundation.e.blisslauncher.features.test.dragndrop.DropTarget
import me.relex.circleindicator.CircleIndicator
import java.util.ArrayList
import java.util.Collections
import java.util.Comparator
class Folder @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AbstractFloatingView(context, attrs), DropTarget, DragController.DragListener,
FolderTitleInput.OnBackKeyListener, FolderItem.FolderListener,
OnEditorActionListener, DragSource {
/**
* Fraction of icon width which behave as scroll region.
*/
private val ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f
private val FOLDER_NAME_ANIMATION_DURATION = 633L
private val REORDER_DELAY = 250L
private val ON_EXIT_CLOSE_DELAY = 400L
private val sTempRect = Rect()
private val MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10
private var sDefaultFolderName: String? = null
private val mReorderAlarm: Alarm = Alarm()
private val mOnExitAlarm: Alarm = Alarm()
val mItemsInReadingOrder = ArrayList<View>()
protected val mLauncher: TestActivity
protected var mDragController: DragController? = null
lateinit var mInfo: FolderItem
private val mCurrentAnimator: AnimatorSet? = null
var mFolderIcon: IconTextView? = null
lateinit var mContent: ViewPager
lateinit var mFolderTitleInput: FolderTitleInput
private lateinit var mPageIndicator: CircleIndicator
// Cell ranks used for drag and drop
var mTargetRank = 0
var mPrevTargetRank = 0
var mEmptyCellRank = 0
var mState: Int = STATE_NONE
private var mRearrangeOnClose = false
var mItemsInvalidated = false
private var mCurrentDragView: View? = null
private var mIsExternalDrag = false
private var mDragInProgress = false
private val mDeleteFolderOnDropCompleted = false
private val mSuppressFolderDeletion = false
private var mItemAddedBackToSelfViaIcon = false
var mFolderIconPivotX = 0f
private var mIsEditingName = false
@ExportedProperty(category = "launcher")
private val mDestroyed = false
init {
setLocaleDependentFields(resources, false /* force */)
mLauncher = TestActivity.getLauncher(context)
isFocusableInTouchMode = true
}
override fun onFinishInflate() {
super.onFinishInflate()
mContent = findViewById(R.id.folder_apps)
mContent.setFolder(this)
mPageIndicator = findViewById(R.id.indicator)
mFolderTitleInput = findViewById(R.id.folder_title)
mFolderTitleInput.setOnBackKeyListener(this)
mFolderTitleInput.setOnFocusChangeListener(this)
mFolderTitleInput.setOnEditorActionListener(this)
mFolderTitleInput.setSelectAllOnFocus(true)
mFolderTitleInput.inputType = mFolderTitleInput.inputType and
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT.inv() and
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS.inv() or
InputType.TYPE_TEXT_FLAG_CAP_WORDS
mFolderTitleInput.forceDisableSuggestions(true)
}
fun startDrag(v: View, options: DragOptions): Boolean {
val tag = v.tag
if (tag is LauncherItem) {
val item: LauncherItem = tag as LauncherItem
mEmptyCellRank = item.cell
mCurrentDragView = v
mDragController!!.addDragListener(this)
mLauncher.getLauncherPagedView().beginDragShared(v, this, options)
}
return true
}
fun setLocaleDependentFields(res: Resources, force: Boolean) {
if (sDefaultFolderName == null || force) {
sDefaultFolderName = res.getString(R.string.untitled)
}
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
override fun handleClose(animate: Boolean) {
}
override fun isOfType(type: Int): Boolean = type and TYPE_FOLDER != 0
override fun onBackKey(): Boolean {
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
val newTitle: String = mFolderTitleInput.text.toString()
mInfo?.setTitle(newTitle)
// Update database
mLauncher.getLauncherPagedView().updateDatabase()
// This ensures that focus is gained every time the field is clicked, which selects all
// the text and brings up the soft keyboard if necessary.
mFolderTitleInput.clearFocus()
Selection.setSelection(mFolderTitleInput.getText(), 0, 0)
mIsEditingName = false
return true
}
override fun onTitleChanged(title: CharSequence?) {
TODO("Not yet implemented")
}
override fun onDragStart(dragObject: DropTarget.DragObject, options: DragOptions) {
if (dragObject.dragSource != this) {
return
}
mContent.removeItem(mCurrentDragView)
if (dragObject.dragInfo is LauncherItem) {
mItemsInvalidated = true
SuppressInfoChanges().use { _ ->
mInfo.remove(
dragObject.dragInfo as WorkspaceItemInfo,
true
)
}
}
mDragInProgress = true
mItemAddedBackToSelfViaIcon = false
}
override fun onDragEnd() {
if (mIsExternalDrag && mDragInProgress) {
completeDragExit()
}
mDragInProgress = false
mDragController?.removeDragListener(this)
}
fun isEditingName(): Boolean {
return mIsEditingName
}
private fun startEditingFolderName() {
post {
mFolderTitleInput.hint = ""
mIsEditingName = true
}
}
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mFolderTitleInput.dispatchBackKey()
return true
}
return false
}
fun getFolderIcon() = mFolderIcon
fun setFolderIcon(icon: IconTextView) {
mFolderIcon = icon
}
override fun onAttachedToWindow() {
// requestFocus() causes the focus onto the folder itself, which doesn't cause visual
// effect but the next arrow key can start the keyboard focus inside of the folder, not
// the folder itself.
requestFocus()
super.onAttachedToWindow()
}
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent?): Boolean =
// When the folder gets focus, we don't want to announce the list of items.
true
override fun focusSearch(direction: Int): View? =
// When the folder is focused, further focus search should be within the folder contents.
FocusFinder.getInstance().findNextFocus(this, null, direction)
/**
* @return the FolderInfo object associated with this folder
*/
fun getInfo(): FolderItem? {
return mInfo
}
fun bind(info: FolderItem) {
mInfo = info
val children: MutableList<LauncherItem> = info.items.toMutableList()
children.sortWith { lhs, rhs ->
lhs.cell - rhs.cell
}
mItemsInvalidated = true
updateTextViewFocus()
mInfo.addListener(this)
mFolderTitleInput.setText(mInfo.title)
mFolderTitleInput.isCursorVisible = false
val mDeviceProfile: VariantDeviceProfile = mLauncher.deviceProfile
mContent?.adapter =
FolderPagerAdapter(context, mInfo.items, mDeviceProfile)
// We use same size for height and width as we want to look it like sqaure
val height =
mDeviceProfile.cellHeightPx * 3 + resources.getDimensionPixelSize(R.dimen.folder_padding)
mContent?.layoutParams?.width =
mDeviceProfile.cellHeightPx * 3 + resources.getDimensionPixelSize(R.dimen.folder_padding) * 2
mContent?.layoutParams?.height =
(mDeviceProfile.cellHeightPx + mDeviceProfile.iconDrawablePaddingPx * 2) * 3 + resources.getDimensionPixelSize(
R.dimen.folder_padding
) * 2
mPageIndicator.setViewPager(mContent)
// In case any children didn't come across during loading, clean up the folder accordingly
mFolderIcon?.post {
if (getItemCount() <= 1) {
replaceFolderWithFinalItem()
}
}
}
fun completeDragExit() {
if (mIsOpen) {
close(true)
mRearrangeOnClose = true
} else if (mState == STATE_ANIMATING) {
mRearrangeOnClose = true
} else {
rearrangeChildren()
clearDragInfo()
}
}
private fun clearDragInfo() {
mCurrentDragView = null
mIsExternalDrag = false
}
/**
* Rearranges the children based on their rank.
*/
fun rearrangeChildren() {
rearrangeChildren(-1)
}
/**
* Rearranges the children based on their rank.
* @param itemCount if greater than the total children count, empty spaces are left at the end,
* otherwise it is ignored.
*/
fun rearrangeChildren(itemCount: Int) {
val views: ArrayList<View> = getItemsInReadingOrder()
mContent.arrangeChildren(views, Math.max(itemCount, views.size))
mItemsInvalidated = true
}
fun getItemCount(): Int {
return mContent.getItemCount()
}
override fun isDropEnabled(): Boolean {
TODO("Not yet implemented")
}
override fun onDrop(dragObject: DropTarget.DragObject?, options: DragOptions?) {
TODO("Not yet implemented")
}
override fun onDragEnter(dragObject: DropTarget.DragObject?) {
TODO("Not yet implemented")
}
override fun onDragOver(dragObject: DropTarget.DragObject?) {
TODO("Not yet implemented")
}
var mOnExitAlarmListener: OnAlarmListener = OnAlarmListener { completeDragExit() }
override fun onDragExit(d: DropTarget.DragObject) {
// We only close the folder if this is a true drag exit, ie. not because
// a drop has occurred above the folder.
if (!d.dragComplete) {
mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener)
mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY)
}
mReorderAlarm.cancelAlarm()
}
override fun acceptDrop(dragObject: DropTarget.DragObject?): Boolean {
TODO("Not yet implemented")
}
override fun prepareAccessibilityDrop() {
TODO("Not yet implemented")
}
override fun onDropCompleted(target: View?, d: DropTarget.DragObject?, success: Boolean) {
}
override fun getHitRectRelativeToDragLayer(outRect: Rect?) {
TODO("Not yet implemented")
}
companion object {
const val STATE_NONE = -1
const val STATE_SMALL = 0
const val STATE_ANIMATING = 1
const val STATE_OPEN = 2
}
/**
* Temporary resource held while we don't want to handle info changes
*/
inner class SuppressInfoChanges internal constructor() : AutoCloseable {
override fun close() {
mInfo.addListener(this@Folder)
updateTextViewFocus()
}
init {
mInfo.removeListener(this@Folder)
}
}
}
\ No newline at end of file
......@@ -31,12 +31,16 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
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;
......@@ -80,6 +84,7 @@ 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;
......@@ -89,6 +94,7 @@ 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,
......@@ -1458,9 +1464,11 @@ public class LauncherPagedView extends PagedView<PageIndicatorDots> implements V
public void onDragStart(
DragObject dragObject, DragOptions options
) {
if (mDragInfo != null && mDragInfo.getCell() != null) {
CellLayout layout = (CellLayout) mDragInfo.getCell().getParent();
layout.markCellsAsUnoccupiedForView(mDragInfo.getCell());
if (mDragInfo != null) {
ViewParent parent = mDragInfo.getCell().getParent();
if (parent instanceof CellLayout) {
((CellLayout) parent).markCellsAsUnoccupiedForView(mDragInfo.getCell());
}
}
if (mOutlineProvider != null) {
......
......@@ -57,8 +57,7 @@ public class ItemLongClickListener {
View v, TestActivity launcher, LauncherItem info,
DragOptions dragOptions
) {
//TODO: Enable when supporting folders
/* if (info.container >= 0) {
if (info.container >= 0) {
Folder folder = Folder.getOpen(launcher);
if (folder != null) {
if (!folder.getItemsInReadingOrder().contains(v)) {
......@@ -68,7 +67,7 @@ public class ItemLongClickListener {
return;
}
}
}*/
}
CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
launcher.getLauncherPagedView().startDrag(longClickCellInfo, dragOptions);
......
......@@ -24,6 +24,8 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.view.View.AccessibilityDelegate;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import foundation.e.blisslauncher.core.utils.ViewCache;
import java.lang.annotation.Retention;
import java.util.ArrayList;
......@@ -87,6 +89,7 @@ public abstract class BaseActivity extends Activity {
return mViewCache;
}
@NonNull
public VariantDeviceProfile getDeviceProfile() {
return mDeviceProfile;
}
......
......@@ -36,8 +36,6 @@ import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import android.provider.Settings
import android.text.Editable
import android.text.InputType
import android.text.Selection
import android.text.TextWatcher
import android.util.Log
import android.view.ContextThemeWrapper
......@@ -130,6 +128,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.observers.DisposableObserver
import io.reactivex.schedulers.Schedulers
import me.relex.circleindicator.CircleIndicator
import java.net.URISyntaxException
import java.util.ArrayList
import java.util.Arrays
......@@ -137,11 +136,9 @@ import java.util.Comparator
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.function.Predicate
import me.relex.circleindicator.CircleIndicator
class TestActivity : BaseDraggingActivity(), AutoCompleteAdapter.OnSuggestionClickListener,
LauncherModel.Callbacks, OnFocusChangeListener, FolderTitleInput.OnBackKeyListener,
OnEditorActionListener {
LauncherModel.Callbacks {
private var mIsEditingName: Boolean = false
private var mModel: LauncherModel? = null
......@@ -445,16 +442,7 @@ class TestActivity : BaseDraggingActivity(), AutoCompleteAdapter.OnSuggestionCli
hotseat = findViewById(R.id.hotseat)
folderContainer = findViewById(R.id.folder_window_container)
mFolderAppsViewPager = findViewById(R.id.folder_apps)
mFolderTitleInput = findViewById(R.id.folder_title)
mFolderTitleInput.setOnBackKeyListener(this)
mFolderTitleInput.setOnFocusChangeListener(this)
mFolderTitleInput.setOnEditorActionListener(this)
mFolderTitleInput.setSelectAllOnFocus(true)
mFolderTitleInput.inputType = mFolderTitleInput.inputType and
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT.inv() and
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS.inv() or
InputType.TYPE_TEXT_FLAG_CAP_WORDS
mFolderTitleInput.forceDisableSuggestions(true)
launcherView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
......@@ -1870,51 +1858,4 @@ class TestActivity : BaseDraggingActivity(), AutoCompleteAdapter.OnSuggestionCli
workspace.removeItemsByMatcher(matcher)
dragController.onAppsRemoved(matcher)
}
override fun onFocusChange(v: View?, hasFocus: Boolean) {
if (v === mFolderTitleInput) {
if (hasFocus) {
startEditingFolderName()
} else {
mFolderTitleInput.dispatchBackKey()
}
}
}
override fun onBackKey(): Boolean {
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
val newTitle: String = mFolderTitleInput.text.toString()
activeFolder?.setTitle(newTitle)
// Update database
workspace.updateDatabase()
// This ensures that focus is gained every time the field is clicked, which selects all
// the text and brings up the soft keyboard if necessary.
mFolderTitleInput.clearFocus()
Selection.setSelection(mFolderTitleInput.getText(), 0, 0)
mIsEditingName = false
return true
}
fun isEditingName(): Boolean {
return mIsEditingName