Add support for viewing folders as dialogs

This commit is contained in:
Ensar Sarajčić 2023-08-01 16:10:05 +02:00
parent a9f879628c
commit 5e58c04338
11 changed files with 401 additions and 59 deletions

View File

@ -63,7 +63,7 @@ android {
}
dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:fad9b2cdb0'
implementation 'com.github.SimpleMobileTools:Simple-Commons:2a2c17151e'
kapt "androidx.room:room-compiler:2.5.2"
implementation "androidx.room:room-runtime:2.5.2"

View File

@ -37,6 +37,7 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.launcher.BuildConfig
import com.simplemobiletools.launcher.R
import com.simplemobiletools.launcher.dialogs.FolderIconsDialog
import com.simplemobiletools.launcher.dialogs.RenameItemDialog
import com.simplemobiletools.launcher.extensions.*
import com.simplemobiletools.launcher.fragments.AllAppsFragment
@ -44,6 +45,7 @@ import com.simplemobiletools.launcher.fragments.MyFragment
import com.simplemobiletools.launcher.fragments.WidgetsFragment
import com.simplemobiletools.launcher.helpers.*
import com.simplemobiletools.launcher.interfaces.FlingListener
import com.simplemobiletools.launcher.interfaces.ItemMenuListener
import com.simplemobiletools.launcher.models.AppLauncher
import com.simplemobiletools.launcher.models.HiddenIcon
import com.simplemobiletools.launcher.models.HomeScreenGridItem
@ -64,6 +66,7 @@ class MainActivity : SimpleActivity(), FlingListener {
private var mIgnoreMoveEvents = false
private var mLongPressedIcon: HomeScreenGridItem? = null
private var mOpenPopupMenu: PopupMenu? = null
private var mOpenFolderDialog: FolderIconsDialog? = null
private var mCachedLaunchers = ArrayList<AppLauncher>()
private var mLastTouchCoords = Pair(-1f, -1f)
private var mActionOnCanBindWidget: ((granted: Boolean) -> Unit)? = null
@ -72,6 +75,52 @@ class MainActivity : SimpleActivity(), FlingListener {
private lateinit var mDetector: GestureDetectorCompat
val menuListener: ItemMenuListener = object : ItemMenuListener {
override fun onAnyClick() {
resetFragmentTouches()
}
override fun hide(gridItem: HomeScreenGridItem) {
hideIcon(gridItem)
}
override fun rename(gridItem: HomeScreenGridItem) {
renameItem(gridItem)
}
override fun resize(gridItem: HomeScreenGridItem) {
home_screen_grid.widgetLongPressed(gridItem)
}
override fun appInfo(gridItem: HomeScreenGridItem) {
launchAppInfo(gridItem.packageName)
}
override fun remove(gridItem: HomeScreenGridItem) {
home_screen_grid.removeAppIcon(gridItem)
}
override fun uninstall(gridItem: HomeScreenGridItem) {
uninstallApp(gridItem.packageName)
}
override fun onDismiss() {
mOpenPopupMenu = null
resetFragmentTouches()
}
override fun beforeShow(menu: Menu) {
var visibleMenuItems = 0
for (item in menu.iterator()) {
if (item.isVisible) {
visibleMenuItems++
}
}
val yOffset = resources.getDimension(R.dimen.long_press_anchor_button_offset_y) * (visibleMenuItems - 1)
home_screen_popup_menu_anchor.y -= yOffset
}
}
companion object {
private var mLastUpEvent = 0L
private const val ANIMATION_DURATION = 150L
@ -330,11 +379,12 @@ class MainActivity : SimpleActivity(), FlingListener {
hasFingerMoved(event)
}
if (mLongPressedIcon != null && mOpenPopupMenu != null && hasFingerMoved) {
if (mLongPressedIcon != null && (mOpenPopupMenu != null || mOpenFolderDialog != null) && hasFingerMoved) {
mOpenPopupMenu?.dismiss()
mOpenPopupMenu = null
home_screen_grid.itemDraggingStarted(mLongPressedIcon!!)
hideFragment(all_apps_fragment)
mOpenFolderDialog?.dismiss()
}
if (mLongPressedIcon != null && hasFingerMoved) {
@ -385,6 +435,7 @@ class MainActivity : SimpleActivity(), FlingListener {
return true
}
// some devices ACTION_MOVE keeps triggering for the whole long press duration, but we are interested in real moves only, when coords change
private fun hasFingerMoved(event: MotionEvent) = mTouchDownX != -1 && mTouchDownY != -1 &&
((Math.abs(mTouchDownX - event.x) > mMoveGestureThreshold) || (Math.abs(mTouchDownY - event.y) > mMoveGestureThreshold))
@ -405,6 +456,7 @@ class MainActivity : SimpleActivity(), FlingListener {
if (hasDeletedAnything) {
home_screen_grid.fetchGridItems()
mOpenFolderDialog?.fetchItems()
}
mCachedLaunchers = launchers
@ -500,18 +552,35 @@ class MainActivity : SimpleActivity(), FlingListener {
}
private fun performItemClick(clickedGridItem: HomeScreenGridItem) {
if (clickedGridItem.type == ITEM_TYPE_ICON) {
launchApp(clickedGridItem.packageName, clickedGridItem.activityName)
} else if (clickedGridItem.type == ITEM_TYPE_SHORTCUT) {
val id = clickedGridItem.shortcutId
val packageName = clickedGridItem.packageName
val userHandle = android.os.Process.myUserHandle()
val shortcutBounds = home_screen_grid.getClickableRect(clickedGridItem)
val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
launcherApps.startShortcut(packageName, id, shortcutBounds, null, userHandle)
when (clickedGridItem.type) {
ITEM_TYPE_ICON -> launchApp(clickedGridItem.packageName, clickedGridItem.activityName)
ITEM_TYPE_FOLDER -> showFolderDialog(clickedGridItem)
ITEM_TYPE_SHORTCUT -> {
val id = clickedGridItem.shortcutId
val packageName = clickedGridItem.packageName
val userHandle = android.os.Process.myUserHandle()
val shortcutBounds = home_screen_grid.getClickableRect(clickedGridItem)
val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
launcherApps.startShortcut(packageName, id, shortcutBounds, null, userHandle)
}
}
}
private fun showFolderDialog(folder: HomeScreenGridItem) {
mOpenFolderDialog = FolderIconsDialog(
activity = this,
folder = folder,
iconWidth = home_screen_grid.getCurrentCellSize(),
iconPadding = home_screen_grid.getCurrentCellMargin(),
dismissListener = {
mOpenFolderDialog = null
},
itemClick = {
performItemClick(it)
}
)
}
private fun performItemLongClick(x: Float, clickedGridItem: HomeScreenGridItem) {
if (clickedGridItem.type == ITEM_TYPE_ICON || clickedGridItem.type == ITEM_TYPE_SHORTCUT) {
main_holder.performHapticFeedback()
@ -521,6 +590,11 @@ class MainActivity : SimpleActivity(), FlingListener {
showHomeIconMenu(x, anchorY, clickedGridItem, false)
}
fun startHandlingItem(gridItem: HomeScreenGridItem) {
mLongPressedIcon = gridItem
mOpenFolderDialog?.dismiss()
}
fun showHomeIconMenu(x: Float, y: Float, gridItem: HomeScreenGridItem, isOnAllAppsFragment: Boolean) {
home_screen_grid.hideResizeLines()
mLongPressedIcon = gridItem
@ -536,7 +610,7 @@ class MainActivity : SimpleActivity(), FlingListener {
home_screen_popup_menu_anchor.y = anchorY
if (mOpenPopupMenu == null) {
mOpenPopupMenu = handleGridItemPopupMenu(home_screen_popup_menu_anchor, gridItem, isOnAllAppsFragment)
mOpenPopupMenu = handleGridItemPopupMenu(home_screen_popup_menu_anchor, gridItem, isOnAllAppsFragment, menuListener)
}
}
@ -564,52 +638,6 @@ class MainActivity : SimpleActivity(), FlingListener {
}
}
private fun handleGridItemPopupMenu(anchorView: View, gridItem: HomeScreenGridItem, isOnAllAppsFragment: Boolean): PopupMenu {
val contextTheme = ContextThemeWrapper(this, getPopupMenuTheme())
return PopupMenu(contextTheme, anchorView, Gravity.TOP or Gravity.END).apply {
if (isQPlus()) {
setForceShowIcon(true)
}
inflate(R.menu.menu_app_icon)
menu.findItem(R.id.rename).isVisible = gridItem.type == ITEM_TYPE_ICON && !isOnAllAppsFragment
menu.findItem(R.id.hide_icon).isVisible = gridItem.type == ITEM_TYPE_ICON && isOnAllAppsFragment
menu.findItem(R.id.resize).isVisible = gridItem.type == ITEM_TYPE_WIDGET
menu.findItem(R.id.app_info).isVisible = gridItem.type == ITEM_TYPE_ICON
menu.findItem(R.id.uninstall).isVisible = gridItem.type == ITEM_TYPE_ICON && canAppBeUninstalled(gridItem.packageName)
menu.findItem(R.id.remove).isVisible = !isOnAllAppsFragment
setOnMenuItemClickListener { item ->
resetFragmentTouches()
when (item.itemId) {
R.id.hide_icon -> hideIcon(gridItem)
R.id.rename -> renameItem(gridItem)
R.id.resize -> home_screen_grid.widgetLongPressed(gridItem)
R.id.app_info -> launchAppInfo(gridItem.packageName)
R.id.remove -> home_screen_grid.removeAppIcon(gridItem)
R.id.uninstall -> uninstallApp(gridItem.packageName)
}
true
}
setOnDismissListener {
mOpenPopupMenu = null
resetFragmentTouches()
}
var visibleMenuItems = 0
for (item in menu.iterator()) {
if (item.isVisible) {
visibleMenuItems++
}
}
val yOffset = resources.getDimension(R.dimen.long_press_anchor_button_offset_y) * (visibleMenuItems - 1)
anchorView.y -= yOffset
show()
}
}
private fun resetFragmentTouches() {
(widgets_fragment as WidgetsFragment).apply {
touchDownY = -1

View File

@ -0,0 +1,111 @@
package com.simplemobiletools.launcher.adapters
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.core.view.iterator
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.launcher.R
import com.simplemobiletools.launcher.activities.MainActivity
import com.simplemobiletools.launcher.extensions.handleGridItemPopupMenu
import com.simplemobiletools.launcher.interfaces.ItemMenuListenerAdapter
import com.simplemobiletools.launcher.models.HomeScreenGridItem
import kotlinx.android.synthetic.main.item_launcher_label.view.launcher_icon
import kotlinx.android.synthetic.main.item_launcher_label.view.launcher_label
import kotlinx.android.synthetic.main.item_launcher_label.view.popup_anchor
class FolderIconsAdapter(
activity: BaseSimpleActivity, var items: MutableList<HomeScreenGridItem>, private val iconPadding: Int,
recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
override fun getActionMenuId() = 0
override fun actionItemPressed(id: Int) {}
override fun getSelectableItemCount() = itemCount
override fun getIsItemSelectable(position: Int) = false
override fun getItemSelectionKey(position: Int) = items.getOrNull(position)?.id?.toInt()
override fun getItemKeyPosition(key: Int) = items.indexOfFirst { it.id?.toInt() == key }
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun prepareActionMode(menu: Menu) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return createViewHolder(R.layout.item_launcher_label, parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
setupView(holder.itemView, item)
bindViewHolder(holder)
}
override fun getItemCount() = items.size
private fun removeItem(item: HomeScreenGridItem) {
val position = items.indexOfFirst { it.id == item.id }
items.removeAt(position)
notifyItemRemoved(position)
}
private fun setupView(view: View, item: HomeScreenGridItem) {
view.apply {
launcher_label.text = item.title
launcher_label.setTextColor(textColor)
launcher_icon.setPadding(iconPadding, iconPadding, iconPadding, 0)
launcher_icon.setImageDrawable(item.drawable)
val mainListener = (activity as? MainActivity)?.menuListener
setOnClickListener { itemClick(item) }
setOnLongClickListener {
popup_anchor.y = launcher_icon.y
activity.handleGridItemPopupMenu(popup_anchor, item, false, object : ItemMenuListenerAdapter() {
override fun appInfo(gridItem: HomeScreenGridItem) {
mainListener?.appInfo(gridItem)
}
override fun remove(gridItem: HomeScreenGridItem) {
mainListener?.remove(gridItem)
removeItem(gridItem)
}
override fun uninstall(gridItem: HomeScreenGridItem) {
mainListener?.uninstall(gridItem)
}
override fun rename(gridItem: HomeScreenGridItem) {
mainListener?.rename(gridItem)
}
override fun beforeShow(menu: Menu) {
var visibleMenuItems = 0
for (menuItem in menu.iterator()) {
if (menuItem.isVisible) {
visibleMenuItems++
}
}
val yOffset = resources.getDimension(R.dimen.long_press_anchor_button_offset_y) * (visibleMenuItems - 1)
popup_anchor.y -= yOffset
}
})
true
}
}
}
fun updateItems(items: List<HomeScreenGridItem>) {
this.items.clear()
this.items.addAll(items)
notifyDataSetChanged()
}
}

View File

@ -0,0 +1,86 @@
package com.simplemobiletools.launcher.dialogs
import android.graphics.drawable.BitmapDrawable
import android.view.View
import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.views.AutoGridLayoutManager
import com.simplemobiletools.launcher.R
import com.simplemobiletools.launcher.adapters.FolderIconsAdapter
import com.simplemobiletools.launcher.extensions.getDrawableForPackageName
import com.simplemobiletools.launcher.extensions.homeScreenGridItemsDB
import com.simplemobiletools.launcher.helpers.ITEM_TYPE_ICON
import com.simplemobiletools.launcher.helpers.ITEM_TYPE_SHORTCUT
import com.simplemobiletools.launcher.models.HomeScreenGridItem
import kotlinx.android.synthetic.main.dialog_folder_icons.view.dialog_folder_icons_grid
class FolderIconsDialog(
val activity: BaseSimpleActivity,
private val folder: HomeScreenGridItem,
private val iconWidth: Int,
private val iconPadding: Int,
private val dismissListener: () -> Unit,
private val itemClick: (HomeScreenGridItem) -> Unit
) {
private var dialog: AlertDialog? = null
private val view: View = activity.layoutInflater.inflate(R.layout.dialog_folder_icons, null)
init {
view.dialog_folder_icons_grid.layoutManager = AutoGridLayoutManager(activity, iconWidth)
ensureBackgroundThread {
val items = activity.homeScreenGridItemsDB.getFolderItems(folder.id!!)
items.forEach { item ->
if (item.type == ITEM_TYPE_ICON) {
item.drawable = activity.getDrawableForPackageName(item.packageName)
} else if (item.type == ITEM_TYPE_SHORTCUT) {
item.drawable = BitmapDrawable(item.icon)
}
}
activity.runOnUiThread {
initDialog(items, view)
}
}
}
private fun initDialog(items: List<HomeScreenGridItem>, view: View) {
view.dialog_folder_icons_grid.adapter = FolderIconsAdapter(activity, items.toMutableList(), iconPadding, view.dialog_folder_icons_grid) {
it as HomeScreenGridItem
itemClick(it)
dialog?.dismiss()
}
activity.getAlertDialogBuilder()
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener { dismissListener() }
.apply {
activity.setupDialogStuff(view, this, 0, folder.title) { alertDialog ->
dialog = alertDialog
}
}
}
fun fetchItems() {
ensureBackgroundThread {
val items = activity.homeScreenGridItemsDB.getFolderItems(folder.id!!)
items.forEach { item ->
if (item.type == ITEM_TYPE_ICON) {
item.drawable = activity.getDrawableForPackageName(item.packageName)
} else if (item.type == ITEM_TYPE_SHORTCUT) {
item.drawable = BitmapDrawable(item.icon)
}
}
activity.runOnUiThread {
(view.dialog_folder_icons_grid.adapter as FolderIconsAdapter).updateItems(items)
}
}
}
fun dismiss() {
dialog?.dismiss()
}
}

View File

@ -6,9 +6,21 @@ import android.content.Intent
import android.content.pm.ApplicationInfo
import android.net.Uri
import android.provider.Settings
import android.view.ContextThemeWrapper
import android.view.Gravity
import android.view.View
import android.widget.PopupMenu
import com.simplemobiletools.commons.extensions.getPopupMenuTheme
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.launcher.R
import com.simplemobiletools.launcher.activities.SettingsActivity
import com.simplemobiletools.launcher.helpers.ITEM_TYPE_FOLDER
import com.simplemobiletools.launcher.helpers.ITEM_TYPE_ICON
import com.simplemobiletools.launcher.helpers.ITEM_TYPE_WIDGET
import com.simplemobiletools.launcher.helpers.UNINSTALL_APP_REQUEST_CODE
import com.simplemobiletools.launcher.interfaces.ItemMenuListener
import com.simplemobiletools.launcher.models.HomeScreenGridItem
fun Activity.launchApp(packageName: String, activityName: String) {
// if this is true, launch the app settings
@ -56,3 +68,40 @@ fun Activity.uninstallApp(packageName: String) {
startActivityForResult(this, UNINSTALL_APP_REQUEST_CODE)
}
}
fun Activity.handleGridItemPopupMenu(anchorView: View, gridItem: HomeScreenGridItem, isOnAllAppsFragment: Boolean, listener: ItemMenuListener): PopupMenu {
val contextTheme = ContextThemeWrapper(this, getPopupMenuTheme())
return PopupMenu(contextTheme, anchorView, Gravity.TOP or Gravity.END).apply {
if (isQPlus()) {
setForceShowIcon(true)
}
inflate(R.menu.menu_app_icon)
menu.findItem(R.id.rename).isVisible = (gridItem.type == ITEM_TYPE_ICON || gridItem.type == ITEM_TYPE_FOLDER) && !isOnAllAppsFragment
menu.findItem(R.id.hide_icon).isVisible = gridItem.type == ITEM_TYPE_ICON && isOnAllAppsFragment
menu.findItem(R.id.resize).isVisible = gridItem.type == ITEM_TYPE_WIDGET
menu.findItem(R.id.app_info).isVisible = gridItem.type == ITEM_TYPE_ICON
menu.findItem(R.id.uninstall).isVisible = gridItem.type == ITEM_TYPE_ICON && canAppBeUninstalled(gridItem.packageName)
menu.findItem(R.id.remove).isVisible = !isOnAllAppsFragment
setOnMenuItemClickListener { item ->
listener.onAnyClick()
when (item.itemId) {
R.id.hide_icon -> listener.hide(gridItem)
R.id.rename -> listener.rename(gridItem)
R.id.resize -> listener.resize(gridItem)
R.id.app_info -> listener.appInfo(gridItem)
R.id.remove -> listener.remove(gridItem)
R.id.uninstall -> listener.uninstall(gridItem)
}
true
}
setOnDismissListener {
listener.onDismiss()
}
listener.beforeShow(menu)
show()
}
}

View File

@ -8,6 +8,9 @@ interface HomeScreenGridItemsDao {
@Query("SELECT * FROM home_screen_grid_items")
fun getAllItems(): List<HomeScreenGridItem>
@Query("SELECT * FROM home_screen_grid_items WHERE parent_id = :folderId")
fun getFolderItems(folderId: Long): List<HomeScreenGridItem>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: HomeScreenGridItem): Long

View File

@ -0,0 +1,28 @@
package com.simplemobiletools.launcher.interfaces
import android.view.Menu
import com.simplemobiletools.launcher.models.HomeScreenGridItem
interface ItemMenuListener {
fun onAnyClick()
fun hide(gridItem: HomeScreenGridItem)
fun rename(gridItem: HomeScreenGridItem)
fun resize(gridItem: HomeScreenGridItem)
fun appInfo(gridItem: HomeScreenGridItem)
fun remove(gridItem: HomeScreenGridItem)
fun uninstall(gridItem: HomeScreenGridItem)
fun onDismiss()
fun beforeShow(menu: Menu)
}
abstract class ItemMenuListenerAdapter : ItemMenuListener {
override fun onAnyClick() = Unit
override fun hide(gridItem: HomeScreenGridItem) = Unit
override fun rename(gridItem: HomeScreenGridItem) = Unit
override fun resize(gridItem: HomeScreenGridItem) = Unit
override fun appInfo(gridItem: HomeScreenGridItem) = Unit
override fun remove(gridItem: HomeScreenGridItem) = Unit
override fun uninstall(gridItem: HomeScreenGridItem) = Unit
override fun onDismiss() = Unit
override fun beforeShow(menu: Menu) = Unit
}

View File

@ -20,7 +20,6 @@ import android.util.Size
import android.util.SizeF
import android.view.View
import android.widget.RelativeLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
@ -1186,6 +1185,10 @@ 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

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dialog_folder_icons_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/small_margin"
android:layout_marginTop="@dimen/medium_margin"
android:layout_marginEnd="@dimen/small_margin"
android:minHeight="@dimen/min_folder_view_height"
app:layout_constraintHeight_max="@dimen/max_folder_view_height">
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/dialog_folder_icons_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:itemCount="10"
tools:listitem="@layout/item_launcher_label" />
</androidx.core.widget.NestedScrollView>
</FrameLayout>

View File

@ -27,4 +27,10 @@
android:maxLines="2"
android:textSize="@dimen/smaller_text_size" />
<View
android:id="@+id/popup_anchor"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center_horizontal|top" />
</RelativeLayout>

View File

@ -9,4 +9,6 @@
<dimen name="page_indicator_dot_radius">6dp</dimen>
<dimen name="page_indicator_stroke_width">1dp</dimen>
<dimen name="page_indicator_margin">6dp</dimen>
<dimen name="min_folder_view_height">200dp</dimen>
<dimen name="max_folder_view_height">500dp</dimen>
</resources>