From a9f879628c3f297b427e7a51b5be250767683c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 1 Aug 2023 12:13:55 +0200 Subject: [PATCH 01/24] Add basic support for folders on home screen --- .../launcher/activities/MainActivity.kt | 16 +- .../launcher/databases/AppsDatabase.kt | 4 +- .../launcher/fragments/AllAppsFragment.kt | 1 + .../launcher/fragments/WidgetsFragment.kt | 1 + .../launcher/helpers/Constants.kt | 1 + .../interfaces/HomeScreenGridItemsDao.kt | 31 ++- .../launcher/models/HomeScreenGridItem.kt | 3 +- .../launcher/views/HomeScreenGrid.kt | 252 +++++++++++++----- 8 files changed, 222 insertions(+), 87 deletions(-) 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 f0768a7..b13c06d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -147,6 +147,7 @@ class MainActivity : SimpleActivity(), FlingListener { shortcutId, icon.toBitmap(), false, + null, icon ) @@ -781,7 +782,8 @@ class MainActivity : SimpleActivity(), FlingListener { -1, "", null, - true + true, + null ) homeScreenGridItems.add(dialerIcon) } @@ -807,7 +809,8 @@ class MainActivity : SimpleActivity(), FlingListener { -1, "", null, - true + true, + null ) homeScreenGridItems.add(SMSMessengerIcon) } @@ -835,7 +838,8 @@ class MainActivity : SimpleActivity(), FlingListener { -1, "", null, - true + true, + null ) homeScreenGridItems.add(browserIcon) } @@ -862,7 +866,8 @@ class MainActivity : SimpleActivity(), FlingListener { -1, "", null, - true + true, + null ) homeScreenGridItems.add(storeIcon) } @@ -891,7 +896,8 @@ class MainActivity : SimpleActivity(), FlingListener { -1, "", null, - true + true, + null ) homeScreenGridItems.add(cameraIcon) } diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/databases/AppsDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/databases/AppsDatabase.kt index 095ea3b..64b4c47 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/databases/AppsDatabase.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/databases/AppsDatabase.kt @@ -68,8 +68,8 @@ abstract class AppsDatabase : RoomDatabase() { private val MIGRATION_4_5 = object : Migration(4, 5) { override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE TABLE `home_screen_grid_items_new` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `left` INTEGER NOT NULL, `top` INTEGER NOT NULL, `right` INTEGER NOT NULL, `bottom` INTEGER NOT NULL, `page` INTEGER NOT NULL, `package_name` TEXT NOT NULL, `activity_name` TEXT NOT NULL, `title` TEXT NOT NULL, `type` INTEGER NOT NULL, `class_name` TEXT NOT NULL, `widget_id` INTEGER NOT NULL, `shortcut_id` TEXT NOT NULL, `icon` BLOB, `docked` INTEGER NOT NULL DEFAULT 0)") - database.execSQL("INSERT INTO `home_screen_grid_items_new` (`id`, `left`, `top`, `right`, `bottom`, `page`, `package_name`, `activity_name`, `title`, `type`, `class_name`, `widget_id`, `shortcut_id`, `icon`, `docked`) SELECT `id`, `left`, `top`, `right`, `bottom`, 0 as `page`, `package_name`, `activity_name`, `title`, `type`, `class_name`, `widget_id`, `shortcut_id`, `icon`, CASE WHEN `type` != 1 AND `top` = 5 THEN 1 ELSE 0 END AS `docked` FROM `home_screen_grid_items` WHERE `intent` IS NULL OR `intent` = ''") + database.execSQL("CREATE TABLE `home_screen_grid_items_new` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `left` INTEGER NOT NULL, `top` INTEGER NOT NULL, `right` INTEGER NOT NULL, `bottom` INTEGER NOT NULL, `page` INTEGER NOT NULL, `package_name` TEXT NOT NULL, `activity_name` TEXT NOT NULL, `title` TEXT NOT NULL, `type` INTEGER NOT NULL, `class_name` TEXT NOT NULL, `widget_id` INTEGER NOT NULL, `shortcut_id` TEXT NOT NULL, `icon` BLOB, `docked` INTEGER NOT NULL DEFAULT 0, `parent_id` INTEGER)") + database.execSQL("INSERT INTO `home_screen_grid_items_new` (`id`, `left`, `top`, `right`, `bottom`, `page`, `package_name`, `activity_name`, `title`, `type`, `class_name`, `widget_id`, `shortcut_id`, `icon`, `docked`, `parent_id`) SELECT `id`, `left`, `top`, `right`, `bottom`, 0 as `page`, `package_name`, `activity_name`, `title`, `type`, `class_name`, `widget_id`, `shortcut_id`, `icon`, CASE WHEN `type` != 1 AND `top` = 5 THEN 1 ELSE 0 END AS `docked`, NULL AS `parent_id` FROM `home_screen_grid_items` WHERE `intent` IS NULL OR `intent` = ''") database.execSQL("DROP TABLE `home_screen_grid_items`") database.execSQL("ALTER TABLE `home_screen_grid_items_new` RENAME TO `home_screen_grid_items`") database.execSQL("CREATE UNIQUE INDEX `index_home_screen_grid_items_id` ON `home_screen_grid_items` (`id`)") diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/AllAppsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/AllAppsFragment.kt index 4ced1d8..e6f5c15 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/AllAppsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/AllAppsFragment.kt @@ -211,6 +211,7 @@ class AllAppsFragment(context: Context, attributeSet: AttributeSet) : MyFragment "", null, false, + null, appLauncher.drawable ) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/WidgetsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/WidgetsFragment.kt index 6d5836d..873e382 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/WidgetsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/WidgetsFragment.kt @@ -266,6 +266,7 @@ class WidgetsFragment(context: Context, attributeSet: AttributeSet) : MyFragment "", null, false, + null, appWidget.widgetPreviewImage, appWidget.providerInfo, appWidget.activityInfo, diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Constants.kt index 65e7bcd..e201a3f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Constants.kt @@ -27,6 +27,7 @@ const val REQUEST_CREATE_SHORTCUT = 53 const val ITEM_TYPE_ICON = 0 const val ITEM_TYPE_WIDGET = 1 const val ITEM_TYPE_SHORTCUT = 2 +const val ITEM_TYPE_FOLDER = 3 const val WIDGET_HOST_ID = 12345 const val MAX_CLICK_DURATION = 150 diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt index 592e895..e96a280 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt @@ -1,9 +1,6 @@ package com.simplemobiletools.launcher.interfaces -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room.* import com.simplemobiletools.launcher.models.HomeScreenGridItem @Dao @@ -23,12 +20,30 @@ interface HomeScreenGridItemsDao { @Query("UPDATE home_screen_grid_items SET title = :title WHERE id = :id") fun updateItemTitle(title: String, id: Long): Int - @Query("UPDATE home_screen_grid_items SET `left` = :left, `top` = :top, `right` = :right, `bottom` = :bottom, `page` = :page, `docked` = :docked WHERE id = :id") - fun updateItemPosition(left: Int, top: Int, right: Int, bottom: Int, page: Int, docked: Boolean, id: Long) + @Query("UPDATE home_screen_grid_items SET `left` = :left, `top` = :top, `right` = :right, `bottom` = :bottom, `page` = :page, `docked` = :docked , `parent_id` = :parentId WHERE id = :id") + fun updateItemPosition(left: Int, top: Int, right: Int, bottom: Int, page: Int, docked: Boolean, parentId: Long?, id: Long) @Query("DELETE FROM home_screen_grid_items WHERE id = :id") - fun deleteById(id: Long) + fun deleteItemById(id: Long) + + @Query("DELETE FROM home_screen_grid_items WHERE parent_id = :id") + fun deleteItemsWithParentId(id: Long) + + @Transaction + fun deleteById(id: Long) { + deleteItemById(id) + deleteItemsWithParentId(id) + } @Query("DELETE FROM home_screen_grid_items WHERE package_name = :packageName") - fun deleteByPackageName(packageName: String) + fun deleteItemByPackageName(packageName: String) + + @Query("DELETE FROM home_screen_grid_items WHERE parent_id IN (SELECT id FROM home_screen_grid_items WHERE package_name = :packageName)") + fun deleteItemsByParentPackageName(packageName: String) + + @Transaction + fun deleteByPackageName(packageName: String) { + deleteItemByPackageName(packageName) + deleteItemsByParentPackageName(packageName) + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt index 7f75d41..7d2791e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt @@ -25,6 +25,7 @@ data class HomeScreenGridItem( @ColumnInfo(name = "shortcut_id") var shortcutId: String, // used at pinned shortcuts at startLauncher call @ColumnInfo(name = "icon") var icon: Bitmap? = null, // store images of pinned shortcuts, those cannot be retrieved after creating @ColumnInfo(name = "docked") var docked: Boolean = false, // special flag, meaning that page, top and bottom don't matter for this item, it is always at the bottom of the screen + @ColumnInfo(name = "parent_id") var parentId: Long? = null, // id of folder this item is in (if it is in any) @Ignore var drawable: Drawable? = null, @Ignore var providerInfo: AppWidgetProviderInfo? = null, // used at widgets @@ -32,7 +33,7 @@ data class HomeScreenGridItem( @Ignore var widthCells: Int = 1, @Ignore var heightCells: Int = 1 ) { - constructor() : this(null, -1, -1, -1, -1, 0, "", "", "", ITEM_TYPE_ICON, "", -1, "", null, false, null, null, null, 1, 1) + constructor() : this(null, -1, -1, -1, -1, 0, "", "", "", ITEM_TYPE_ICON, "", -1, "", null, false, null, null, null, null, 1, 1) fun getWidthInCells() = if (right == -1 || left == -1) { widthCells 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 4e18bcd..f31d58e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -20,6 +20,7 @@ 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 @@ -153,6 +154,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel gridItems.forEach { item -> if (item.type == ITEM_TYPE_ICON) { item.drawable = context.getDrawableForPackageName(item.packageName) + } else if (item.type == ITEM_TYPE_FOLDER) { + item.drawable = resources.getColoredDrawableWithColor(R.drawable.ic_folder_vector, context.getProperPrimaryColor()) } else if (item.type == ITEM_TYPE_SHORTCUT) { if (item.icon != null) { item.drawable = BitmapDrawable(item.icon) @@ -215,7 +218,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fun itemDraggingStarted(draggedGridItem: HomeScreenGridItem) { draggedItem = draggedGridItem if (draggedItem!!.drawable == null) { - draggedItem!!.drawable = context.getDrawableForPackageName(draggedGridItem.packageName) + if (draggedItem?.type == ITEM_TYPE_FOLDER) { + draggedItem!!.drawable = resources.getColoredDrawableWithColor(R.drawable.ic_folder_vector, context.getProperPrimaryColor()) + } else { + draggedItem!!.drawable = context.getDrawableForPackageName(draggedGridItem.packageName) + } } redrawGrid() @@ -293,6 +300,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel scheduleIndicatorsFade() when (draggedItem!!.type) { + ITEM_TYPE_FOLDER -> moveItem() ITEM_TYPE_ICON, ITEM_TYPE_SHORTCUT -> addAppIconOrShortcut() ITEM_TYPE_WIDGET -> addWidget() } @@ -328,7 +336,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } updateWidgetPositionAndSize(widgetView, item) ensureBackgroundThread { - context.homeScreenGridItemsDB.updateItemPosition(item.left, item.top, item.right, item.bottom, item.page, false, item.id!!) + context.homeScreenGridItemsDB.updateItemPosition(item.left, item.top, item.right, item.bottom, item.page, false, null, item.id!!) } } @@ -353,6 +361,58 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel resizedWidget = null } + private fun moveItem() { + val draggedHomeGridItem = gridItems.firstOrNull { it.id == draggedItem?.id } + val center = gridCenters.minBy { + abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + } + + var redrawIcons = false + val gridCells = getClosestGridCells(center) + if (gridCells != null) { + val xIndex = gridCells.first + val yIndex = gridCells.second + + // check if the destination cell is empty + var isDroppingPositionValid = true + val wantedCell = Pair(xIndex, yIndex) + gridItems.filterVisibleOnly().forEach { item -> + for (xCell in item.left..item.right) { + for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { + val cell = Pair(xCell, yCell) + val isAnyCellOccupied = wantedCell == cell + if (isAnyCellOccupied) { + isDroppingPositionValid = false + return@forEach + } + } + } + } + + if (isDroppingPositionValid) { + draggedHomeGridItem?.apply { + left = xIndex + top = yIndex + right = xIndex + bottom = yIndex + page = currentPage + docked = yIndex == rowCount - 1 + + ensureBackgroundThread { + context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, parentId, id!!) + } + } + redrawIcons = true + } + } + + draggedItem = null + draggedItemCurrentCoords = Pair(-1, -1) + if (redrawIcons) { + redrawGrid() + } + } + private fun addAppIconOrShortcut() { val center = gridCenters.minBy { Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) @@ -364,78 +424,56 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val xIndex = gridCells.first val yIndex = gridCells.second - // check if the destination cell is empty - var areAllCellsEmpty = true + // check if the destination cell is empty or a folder + var isDroppingPositionValid = true + var potentialParent: HomeScreenGridItem? = null val wantedCell = Pair(xIndex, yIndex) - gridItems.filter { it.page == currentPage || it.docked }.forEach { item -> + gridItems.filterVisibleOnly().forEach { item -> for (xCell in item.left..item.right) { for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) val isAnyCellOccupied = wantedCell == cell if (isAnyCellOccupied) { - areAllCellsEmpty = false + if (item.type != ITEM_TYPE_WIDGET) { + potentialParent = item + } else { + isDroppingPositionValid = false + } return@forEach } } } } - if (areAllCellsEmpty) { + if (isDroppingPositionValid) { val draggedHomeGridItem = gridItems.firstOrNull { it.id == draggedItem?.id } - // we are moving an existing home screen item from one place to another - if (draggedHomeGridItem != null) { - draggedHomeGridItem.apply { - left = xIndex - top = yIndex - right = xIndex - bottom = yIndex - page = currentPage - docked = yIndex == rowCount - 1 - + if (potentialParent != null) { + if (potentialParent?.type == ITEM_TYPE_FOLDER) { + addAppIconOrShortcut(draggedHomeGridItem, xIndex, yIndex, potentialParent?.id) + } else { + val parentItem = potentialParent!!.copy( + type = ITEM_TYPE_FOLDER, + id = null, + title = resources.getString(R.string.folder) + ) ensureBackgroundThread { - context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, id!!) - } - } - redrawIcons = true - } else if (draggedItem != null) { - // we are dragging a new item at the home screen from the All Apps fragment - val newHomeScreenGridItem = HomeScreenGridItem( - null, - xIndex, - yIndex, - xIndex, - yIndex, - currentPage, - draggedItem!!.packageName, - draggedItem!!.activityName, - draggedItem!!.title, - draggedItem!!.type, - "", - -1, - "", - draggedItem!!.icon, - yIndex == rowCount - 1, - draggedItem!!.drawable, - draggedItem!!.providerInfo, - draggedItem!!.activityInfo - ) - - if (newHomeScreenGridItem.type == ITEM_TYPE_ICON) { - ensureBackgroundThread { - storeAndShowGridItem(newHomeScreenGridItem) - } - } else if (newHomeScreenGridItem.type == ITEM_TYPE_SHORTCUT) { - (context as? MainActivity)?.handleShorcutCreation(newHomeScreenGridItem.activityInfo!!) { shortcutId, label, icon -> - ensureBackgroundThread { - newHomeScreenGridItem.shortcutId = shortcutId - newHomeScreenGridItem.title = label - newHomeScreenGridItem.icon = icon.toBitmap() - newHomeScreenGridItem.drawable = icon - storeAndShowGridItem(newHomeScreenGridItem) + val newId = context.homeScreenGridItemsDB.insert(parentItem) + parentItem.id = newId + potentialParent?.apply { + parentId = newId + context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newId, id!!) + } + (context as? MainActivity)?.runOnUiThread { + gridItems.add(parentItem) + addAppIconOrShortcut(draggedHomeGridItem, xIndex, yIndex, newId) } } } + return + } else { + addAppIconOrShortcut(draggedHomeGridItem, xIndex, yIndex) + return } } else { performHapticFeedback() @@ -450,6 +488,68 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + private fun addAppIconOrShortcut(draggedHomeGridItem: HomeScreenGridItem?, xIndex: Int, yIndex: Int, newParentId: Long? = null) { + // we are moving an existing home screen item from one place to another + if (draggedHomeGridItem != null) { + draggedHomeGridItem.apply { + left = xIndex + top = yIndex + right = xIndex + bottom = yIndex + page = currentPage + docked = yIndex == rowCount - 1 + parentId = newParentId + + ensureBackgroundThread { + context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newParentId, id!!) + } + } + } else if (draggedItem != null) { + // we are dragging a new item at the home screen from the All Apps fragment + val newHomeScreenGridItem = HomeScreenGridItem( + null, + xIndex, + yIndex, + xIndex, + yIndex, + currentPage, + draggedItem!!.packageName, + draggedItem!!.activityName, + draggedItem!!.title, + draggedItem!!.type, + "", + -1, + "", + draggedItem!!.icon, + yIndex == rowCount - 1, + newParentId, + draggedItem!!.drawable, + draggedItem!!.providerInfo, + draggedItem!!.activityInfo + ) + + if (newHomeScreenGridItem.type == ITEM_TYPE_ICON) { + ensureBackgroundThread { + storeAndShowGridItem(newHomeScreenGridItem) + } + } else if (newHomeScreenGridItem.type == ITEM_TYPE_SHORTCUT) { + (context as? MainActivity)?.handleShorcutCreation(newHomeScreenGridItem.activityInfo!!) { shortcutId, label, icon -> + ensureBackgroundThread { + newHomeScreenGridItem.shortcutId = shortcutId + newHomeScreenGridItem.title = label + newHomeScreenGridItem.icon = icon.toBitmap() + newHomeScreenGridItem.drawable = icon + storeAndShowGridItem(newHomeScreenGridItem) + } + } + } + } + + draggedItem = null + draggedItemCurrentCoords = Pair(-1, -1) + redrawGrid() + } + fun storeAndShowGridItem(item: HomeScreenGridItem) { val newId = context.homeScreenGridItemsDB.insert(item) item.id = newId @@ -473,7 +573,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } var areAllCellsEmpty = true - gridItems.filter { it.id != draggedItem?.id && (it.page == currentPage || it.docked) }.forEach { item -> + gridItems.filterVisibleOnly().filter { it.id != draggedItem?.id }.forEach { item -> for (xCell in item.left..item.right) { for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) @@ -512,6 +612,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel widgetItem.bottom, currentPage, false, + null, widgetItem.id!! ) val widgetView = widgetViews.firstOrNull { it.tag == widgetItem.widgetId } @@ -694,13 +795,19 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (item.id != draggedItem?.id) { val drawableX = cellXCoords[item.left] + iconMargin + extraXMargin + sideMargins.left + (width * xFactor).toInt() + val drawable = if (item.type == ITEM_TYPE_FOLDER) { + resources.getColoredDrawableWithColor(R.drawable.ic_folder_vector, context.getProperPrimaryColor()) + } else { + item.drawable!! + } + if (item.docked) { val drawableY = cellYCoords[rowCount - 1] + cellHeight - iconMargin - iconSize + sideMargins.top - item.drawable!!.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) + drawable.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) } else { val drawableY = cellYCoords[item.top] + iconMargin + extraYMargin + sideMargins.top - item.drawable!!.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) + drawable.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) if (item.id != draggedItem?.id && item.title.isNotEmpty()) { val textX = cellXCoords[item.left].toFloat() + labelSideMargin + sideMargins.left + width * xFactor @@ -719,11 +826,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - item.drawable!!.draw(canvas) + drawable.draw(canvas) } } - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.page == currentPage && !it.docked } + 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 } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -731,15 +838,16 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel handleItemDrawing(item, currentXFactor) } - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.docked }.forEach { item -> - if (item.outOfBounds()) { - return@forEach - } + gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && it.docked && it.parentId == null } + .forEach { item -> + if (item.outOfBounds()) { + return@forEach + } - handleItemDrawing(item, 0f) - } + handleItemDrawing(item, 0f) + } if (pageChangeAnimLeftPercentage > 0f) { - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.page == lastPage && !it.docked } + 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 } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -794,7 +902,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { - if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT) { + if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) { // draw a circle under the current cell val center = gridCenters.minBy { abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) @@ -936,12 +1044,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } fun isClickingGridItem(x: Int, y: Int): HomeScreenGridItem? { - for (gridItem in gridItems.filter { it.page == currentPage || it.docked }) { + for (gridItem in gridItems.filterVisibleOnly()) { if (gridItem.outOfBounds()) { continue } - if (gridItem.type == ITEM_TYPE_ICON || gridItem.type == ITEM_TYPE_SHORTCUT) { + if (gridItem.type == ITEM_TYPE_ICON || gridItem.type == ITEM_TYPE_SHORTCUT || gridItem.type == ITEM_TYPE_FOLDER) { val rect = getClickableRect(gridItem) if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { return gridItem @@ -1106,6 +1214,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + private fun ArrayList.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null } + companion object { private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L From 5e58c0433841809f373add04fce14213f496ee5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 1 Aug 2023 16:10:05 +0200 Subject: [PATCH 02/24] Add support for viewing folders as dialogs --- app/build.gradle | 2 +- .../launcher/activities/MainActivity.kt | 142 +++++++++++------- .../launcher/adapters/FolderIconsAdapter.kt | 111 ++++++++++++++ .../launcher/dialogs/FolderIconsDialog.kt | 86 +++++++++++ .../launcher/extensions/Activity.kt | 49 ++++++ .../interfaces/HomeScreenGridItemsDao.kt | 3 + .../launcher/interfaces/ItemMenuListener.kt | 28 ++++ .../launcher/views/HomeScreenGrid.kt | 5 +- .../main/res/layout/dialog_folder_icons.xml | 26 ++++ .../main/res/layout/item_launcher_label.xml | 6 + app/src/main/res/values/dimens.xml | 2 + 11 files changed, 401 insertions(+), 59 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/launcher/adapters/FolderIconsAdapter.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt create mode 100644 app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/ItemMenuListener.kt create mode 100644 app/src/main/res/layout/dialog_folder_icons.xml diff --git a/app/build.gradle b/app/build.gradle index 29177a0..1ec6d74 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" 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 b13c06d..ab9c7fe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -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() 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 diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/adapters/FolderIconsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/adapters/FolderIconsAdapter.kt new file mode 100644 index 0000000..2564373 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/adapters/FolderIconsAdapter.kt @@ -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, 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) { + this.items.clear() + this.items.addAll(items) + notifyDataSetChanged() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt new file mode 100644 index 0000000..c8dd1a3 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt @@ -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, 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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/extensions/Activity.kt index be02efb..316fd2e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/extensions/Activity.kt @@ -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() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt index e96a280..c7fb8a3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt @@ -8,6 +8,9 @@ interface HomeScreenGridItemsDao { @Query("SELECT * FROM home_screen_grid_items") fun getAllItems(): List + @Query("SELECT * FROM home_screen_grid_items WHERE parent_id = :folderId") + fun getFolderItems(folderId: Long): List + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(item: HomeScreenGridItem): Long diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/ItemMenuListener.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/ItemMenuListener.kt new file mode 100644 index 0000000..4f0a324 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/ItemMenuListener.kt @@ -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 +} 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 f31d58e..516b7a3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -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 diff --git a/app/src/main/res/layout/dialog_folder_icons.xml b/app/src/main/res/layout/dialog_folder_icons.xml new file mode 100644 index 0000000..3e30ac8 --- /dev/null +++ b/app/src/main/res/layout/dialog_folder_icons.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/item_launcher_label.xml b/app/src/main/res/layout/item_launcher_label.xml index 5d8a910..431a2c4 100644 --- a/app/src/main/res/layout/item_launcher_label.xml +++ b/app/src/main/res/layout/item_launcher_label.xml @@ -27,4 +27,10 @@ android:maxLines="2" android:textSize="@dimen/smaller_text_size" /> + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 18d61a4..d8beb5c 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,4 +9,6 @@ 6dp 1dp 6dp + 200dp + 500dp From c049b41c1a88e16df754da98f251c27e30fe7420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Thu, 17 Aug 2023 17:13:40 +0200 Subject: [PATCH 03/24] Use property delegates for viewbinding in activities --- .../launcher/activities/HiddenIconsActivity.kt | 4 ++-- .../com/simplemobiletools/launcher/activities/MainActivity.kt | 3 +-- .../simplemobiletools/launcher/activities/SettingsActivity.kt | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/HiddenIconsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/HiddenIconsActivity.kt index c45dc41..6c036a5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/HiddenIconsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/HiddenIconsActivity.kt @@ -5,6 +5,7 @@ import android.content.pm.PackageManager import android.os.Bundle import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.normalizeString +import com.simplemobiletools.commons.extensions.viewBinding import com.simplemobiletools.commons.helpers.NavigationIcon import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener @@ -17,12 +18,11 @@ import com.simplemobiletools.launcher.extensions.hiddenIconsDB import com.simplemobiletools.launcher.models.HiddenIcon class HiddenIconsActivity : SimpleActivity(), RefreshRecyclerViewListener { - private lateinit var binding: ActivityHiddenIconsBinding + private val binding by viewBinding(ActivityHiddenIconsBinding::inflate) override fun onCreate(savedInstanceState: Bundle?) { isMaterialActivity = true super.onCreate(savedInstanceState) - binding = ActivityHiddenIconsBinding.inflate(layoutInflater) setContentView(binding.root) updateIcons() 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 c771ccd..6f881e0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -73,7 +73,7 @@ class MainActivity : SimpleActivity(), FlingListener { private var wasJustPaused: Boolean = false private lateinit var mDetector: GestureDetectorCompat - private lateinit var binding: ActivityMainBinding + private val binding by viewBinding(ActivityMainBinding::inflate) val menuListener: ItemMenuListener = object : ItemMenuListener { override fun onAnyClick() { @@ -131,7 +131,6 @@ class MainActivity : SimpleActivity(), FlingListener { useDynamicTheme = false super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) appLaunched(BuildConfig.APPLICATION_ID) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/SettingsActivity.kt index 98f993a..eadbc65 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/SettingsActivity.kt @@ -21,11 +21,10 @@ import kotlin.system.exitProcess class SettingsActivity : SimpleActivity() { - private lateinit var binding: ActivitySettingsBinding + private val binding by viewBinding(ActivitySettingsBinding::inflate) override fun onCreate(savedInstanceState: Bundle?) { isMaterialActivity = true super.onCreate(savedInstanceState) - binding = ActivitySettingsBinding.inflate(layoutInflater) setContentView(binding.root) updateMaterialActivityViews(binding.settingsCoordinator, binding.settingsHolder, useTransparentNavigation = true, useTopSearchMenu = false) From 62a3faf8eace4888307814ceb2f5b03b9ff42d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 14:40:10 +0200 Subject: [PATCH 04/24] Move from folder dialog to custom folder drawing --- .../launcher/activities/MainActivity.kt | 31 +--- .../launcher/dialogs/FolderIconsDialog.kt | 84 ---------- .../launcher/views/HomeScreenGrid.kt | 143 +++++++++++++++--- 3 files changed, 129 insertions(+), 129 deletions(-) delete mode 100644 app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt 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 6f881e0..ddb5d48 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -38,7 +38,6 @@ 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.databinding.ActivityMainBinding import com.simplemobiletools.launcher.databinding.AllAppsFragmentBinding import com.simplemobiletools.launcher.databinding.WidgetsFragmentBinding @@ -64,7 +63,6 @@ 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() private var mLastTouchCoords = Pair(-1f, -1f) private var mActionOnCanBindWidget: ((granted: Boolean) -> Unit)? = null @@ -398,12 +396,11 @@ class MainActivity : SimpleActivity(), FlingListener { hasFingerMoved(event) } - if (mLongPressedIcon != null && (mOpenPopupMenu != null || mOpenFolderDialog != null) && hasFingerMoved) { + if (mLongPressedIcon != null && (mOpenPopupMenu != null) && hasFingerMoved) { mOpenPopupMenu?.dismiss() mOpenPopupMenu = null binding.homeScreenGrid.root.itemDraggingStarted(mLongPressedIcon!!) hideFragment(binding.allAppsFragment) - mOpenFolderDialog?.dismiss() } if (mLongPressedIcon != null && hasFingerMoved) { @@ -475,7 +472,6 @@ class MainActivity : SimpleActivity(), FlingListener { if (hasDeletedAnything) { binding.homeScreenGrid.root.fetchGridItems() - mOpenFolderDialog?.fetchItems() } mCachedLaunchers = launchers @@ -558,6 +554,9 @@ class MainActivity : SimpleActivity(), FlingListener { if (clickedGridItem != null) { performItemClick(clickedGridItem) } + if (clickedGridItem?.type != ITEM_TYPE_FOLDER) { + binding.homeScreenGrid.root.closeFolder(redraw = true) + } } fun closeAppDrawer(delayed: Boolean = false) { @@ -595,7 +594,7 @@ class MainActivity : SimpleActivity(), FlingListener { private fun performItemClick(clickedGridItem: HomeScreenGridItem) { when (clickedGridItem.type) { ITEM_TYPE_ICON -> launchApp(clickedGridItem.packageName, clickedGridItem.activityName) - ITEM_TYPE_FOLDER -> showFolderDialog(clickedGridItem) + ITEM_TYPE_FOLDER -> openFolder(clickedGridItem) ITEM_TYPE_SHORTCUT -> { val id = clickedGridItem.shortcutId val packageName = clickedGridItem.packageName @@ -607,19 +606,8 @@ class MainActivity : SimpleActivity(), FlingListener { } } - private fun showFolderDialog(folder: HomeScreenGridItem) { - mOpenFolderDialog = FolderIconsDialog( - activity = this, - folder = folder, - iconWidth = binding.homeScreenGrid.root.getCurrentCellSize(), - iconPadding = binding.homeScreenGrid.root.getCurrentCellMargin(), - dismissListener = { - mOpenFolderDialog = null - }, - itemClick = { - performItemClick(it) - } - ) + private fun openFolder(folder: HomeScreenGridItem) { + binding.homeScreenGrid.root.openFolder(folder) } private fun performItemLongClick(x: Float, clickedGridItem: HomeScreenGridItem) { @@ -631,11 +619,6 @@ 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) { binding.homeScreenGrid.root.hideResizeLines() mLongPressedIcon = gridItem diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt deleted file mode 100644 index c5fc120..0000000 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/dialogs/FolderIconsDialog.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.simplemobiletools.launcher.dialogs - -import android.graphics.drawable.BitmapDrawable -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.adapters.FolderIconsAdapter -import com.simplemobiletools.launcher.databinding.DialogFolderIconsBinding -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 - -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 binding = DialogFolderIconsBinding.inflate(activity.layoutInflater) - - init { - - binding.dialogFolderIconsGrid.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, binding) - } - } - } - - private fun initDialog(items: List, binding: DialogFolderIconsBinding) { - binding.dialogFolderIconsGrid.adapter = FolderIconsAdapter(activity, items.toMutableList(), iconPadding, binding.dialogFolderIconsGrid) { - it as HomeScreenGridItem - itemClick(it) - dialog?.dismiss() - } - - activity.getAlertDialogBuilder() - .setNegativeButton(com.simplemobiletools.commons.R.string.cancel, null) - .setOnDismissListener { dismissListener() } - .apply { - activity.setupDialogStuff(binding.root, 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 { - (binding.dialogFolderIconsGrid.adapter as FolderIconsAdapter).updateItems(items) - } - } - } - - fun dismiss() { - dialog?.dismiss() - } -} 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 39db7b6..7335b5f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -22,6 +22,7 @@ import android.view.View import android.widget.RelativeLayout import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable +import androidx.core.graphics.toRectF import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.customview.widget.ExploreByTouchHelper @@ -37,10 +38,7 @@ import com.simplemobiletools.launcher.extensions.getDrawableForPackageName import com.simplemobiletools.launcher.extensions.homeScreenGridItemsDB import com.simplemobiletools.launcher.helpers.* import com.simplemobiletools.launcher.models.HomeScreenGridItem -import kotlin.math.abs -import kotlin.math.floor -import kotlin.math.max -import kotlin.math.min +import kotlin.math.* class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : RelativeLayout(context, attrs, defStyle) { constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) @@ -64,6 +62,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var dragShadowCirclePaint: Paint private var emptyPageIndicatorPaint: Paint private var currentPageIndicatorPaint: Paint + private var folderBackgroundPaint: Paint private var draggedItem: HomeScreenGridItem? = null private var resizedWidget: HomeScreenGridItem? = null private var isFirstDraw = true @@ -78,6 +77,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var pageChangeEnabled = true private var pageChangeIndicatorsAlpha = 0f + private var currentlyOpenFolder: HomeScreenGridItem? = null + // apply fake margins at the home screen. Real ones would cause the icons be cut at dragging at screen sides var sideMargins = Rect() @@ -136,6 +137,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel style = Paint.Style.FILL } + folderBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getProperBackgroundColor() + style = Paint.Style.FILL + } + val sideMargin = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.normal_margin).toInt() sideMargins.apply { top = context.statusBarHeight @@ -469,6 +475,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel parentItem.id = newId potentialParent?.apply { parentId = newId + left = 0 context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newId, id!!) } (context as? MainActivity)?.runOnUiThread { @@ -496,12 +503,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun addAppIconOrShortcut(draggedHomeGridItem: HomeScreenGridItem?, xIndex: Int, yIndex: Int, newParentId: Long? = null) { + val finalXIndex = if (newParentId != null) { + gridItems.find { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 + } else { + xIndex + } // we are moving an existing home screen item from one place to another if (draggedHomeGridItem != null) { draggedHomeGridItem.apply { - left = xIndex + left = finalXIndex top = yIndex - right = xIndex + right = finalXIndex bottom = yIndex page = currentPage docked = yIndex == rowCount - 1 @@ -515,9 +527,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel // we are dragging a new item at the home screen from the All Apps fragment val newHomeScreenGridItem = HomeScreenGridItem( null, - xIndex, + finalXIndex, yIndex, - xIndex, + finalXIndex, yIndex, currentPage, draggedItem!!.packageName, @@ -798,9 +810,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel 1 - pageChangeAnimLeftPercentage } - fun handleItemDrawing(item: HomeScreenGridItem, xFactor: Float) { + fun handleGridItemDrawing(item: HomeScreenGridItem, baseItemX: Int, baseItemY: Int) { if (item.id != draggedItem?.id) { - val drawableX = cellXCoords[item.left] + iconMargin + extraXMargin + sideMargins.left + (width * xFactor).toInt() + val drawableX = baseItemX + iconMargin + extraXMargin val drawable = if (item.type == ITEM_TYPE_FOLDER) { resources.getColoredDrawableWithColor(com.simplemobiletools.commons.R.drawable.ic_folder_vector, context.getProperPrimaryColor()) @@ -813,12 +825,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel drawable.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) } else { - val drawableY = cellYCoords[item.top] + iconMargin + extraYMargin + sideMargins.top + val drawableY = baseItemY + iconMargin + extraYMargin drawable.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) if (item.id != draggedItem?.id && item.title.isNotEmpty()) { - val textX = cellXCoords[item.left].toFloat() + labelSideMargin + sideMargins.left + width * xFactor - val textY = cellYCoords[item.top].toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin + sideMargins.top + val textX = baseItemX.toFloat() + labelSideMargin + val textY = baseItemY.toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin val staticLayout = StaticLayout.Builder .obtain(item.title, 0, item.title.length, textPaint, cellWidth - 2 * labelSideMargin) .setMaxLines(2) @@ -837,13 +849,21 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + fun handleMainGridItemDrawing(item: HomeScreenGridItem, xFactor: Float) { + handleGridItemDrawing( + item, + cellXCoords[item.left] + sideMargins.left + (width * xFactor).toInt(), + cellYCoords[item.top] + sideMargins.top + ) + } + 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 } .forEach { item -> if (item.outOfBounds()) { return@forEach } - handleItemDrawing(item, currentXFactor) + handleMainGridItemDrawing(item, currentXFactor) } gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && it.docked && it.parentId == null } .forEach { item -> @@ -851,7 +871,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return@forEach } - handleItemDrawing(item, 0f) + 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 } @@ -860,7 +880,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return@forEach } - handleItemDrawing(item, lastXFactor) + handleMainGridItemDrawing(item, lastXFactor) } } @@ -966,6 +986,23 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + val folder = currentlyOpenFolder + if (folder != null) { + val items = folder.getFolderItems() + val folderRect = folder.getFolderRect() + + canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) + + items.forEach { item -> + val (row, column) = item.getPositionInFolder(folder) + handleGridItemDrawing( + item, + (folderRect.left + column * cellWidth).roundToInt(), + (folderRect.top + row * cellHeight).roundToInt() + ) + } + } + isFirstDraw = false } @@ -1016,12 +1053,23 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fillCellSizes() } - val clickableLeft = cellXCoords[item.left] + sideMargins.left + extraXMargin - val clickableTop = if (item.docked) { - cellYCoords[item.getDockAdjustedTop(rowCount)] + cellHeight - iconSize - iconMargin + val folder = currentlyOpenFolder + val clickableLeft: Int + val clickableTop: Int + if (folder != null && item.parentId == folder.id) { + val folderRect = folder.getFolderRect() + val (row, column) = item.getPositionInFolder(folder) + clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt() + clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() } else { - cellYCoords[item.top] - iconMargin + extraYMargin - } + sideMargins.top + clickableLeft = cellXCoords[item.left] + sideMargins.left + extraXMargin + clickableTop = if (item.docked) { + cellYCoords[item.getDockAdjustedTop(rowCount)] + cellHeight - iconSize - iconMargin + } else { + cellYCoords[item.top] - iconMargin + extraYMargin + } + sideMargins.top + } + return Rect(clickableLeft, clickableTop, clickableLeft + iconSize + 2 * iconMargin, clickableTop + iconSize + 2 * iconMargin) } @@ -1051,6 +1099,15 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } fun isClickingGridItem(x: Int, y: Int): HomeScreenGridItem? { + currentlyOpenFolder?.also { folder -> + folder.getFolderItems().forEach { gridItem -> + val rect = getClickableRect(gridItem) + if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { + return gridItem + } + } + } + for (gridItem in gridItems.filterVisibleOnly()) { if (gridItem.outOfBounds()) { continue @@ -1191,6 +1248,18 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return false } + fun openFolder(folder: HomeScreenGridItem) { + currentlyOpenFolder = folder + redrawGrid() + } + + fun closeFolder(redraw: Boolean = false) { + currentlyOpenFolder = null + if (redraw) { + redrawGrid() + } + } + fun getCurrentIconSize(): Int = iconSize fun getCurrentCellSize(): Int = cellWidth @@ -1200,6 +1269,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun handlePageChange(redraw: Boolean = false) { pageChangeEnabled = false pageChangeIndicatorsAlpha = 0f + closeFolder() removeCallbacks(startFadingIndicators) if (redraw) { redrawGrid() @@ -1227,6 +1297,37 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun ArrayList.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null } + private fun HomeScreenGridItem.getFolderItems() = gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == id } + + private fun HomeScreenGridItem.getFolderRect(): RectF { + val count = getFolderItems().count() + val columnsCount = if (count == 2) { + 2 + } else { + ceil(count / 2.0f).roundToInt() + } + val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() + val centerX = cellXCoords[left] + cellWidth / 2 + val centerY = cellYCoords[top] + cellHeight / 2 + val folderDialogWidth = (columnsCount * cellWidth).toFloat() + val folderDialogHeight = (rowsCount * cellHeight).toFloat() + val folderDialogTop = centerY - folderDialogHeight / 2 + sideMargins.top + val folderDialogLeft = centerX - folderDialogWidth / 2 + sideMargins.left + return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight) + } + + private fun HomeScreenGridItem.getPositionInFolder(folder: HomeScreenGridItem): Pair { + val count = folder.getFolderItems().count() + val columnsCount = if (count == 2) { + 2 + } else { + ceil(count / 2.0f).roundToInt() + } + val column = left % columnsCount + val row = left / columnsCount + return Pair(row, column) + } + companion object { private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L From 55ebb2eb15bca1446fe77e4a3fd9804998fcf3b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 14:58:27 +0200 Subject: [PATCH 05/24] Close folder when dragging items outside of it --- .../launcher/views/HomeScreenGrid.kt | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) 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 7335b5f..8716426 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -78,6 +78,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var pageChangeIndicatorsAlpha = 0f private var currentlyOpenFolder: HomeScreenGridItem? = null + private var draggingLeftFolderAt: Long? = null // apply fake margins at the home screen. Real ones would cause the icons be cut at dragging at screen sides var sideMargins = Rect() @@ -246,6 +247,20 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return } + currentlyOpenFolder?.also { folder -> + if (folder.getFolderRect().contains(x.toFloat(), y.toFloat())) { + draggingLeftFolderAt = null + } else { + draggingLeftFolderAt.also { + if (it == null) { + draggingLeftFolderAt = System.currentTimeMillis() + } else if (System.currentTimeMillis() - it > PAGE_CHANGE_HOLD_THRESHOLD) { + closeFolder() + } + } + } + } + pageChangeIndicatorsAlpha = 1f removeCallbacks(startFadingIndicators) if (draggedItemCurrentCoords.first == -1 && draggedItemCurrentCoords.second == -1 && draggedItem != null) { @@ -504,13 +519,18 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun addAppIconOrShortcut(draggedHomeGridItem: HomeScreenGridItem?, xIndex: Int, yIndex: Int, newParentId: Long? = null) { val finalXIndex = if (newParentId != null) { - gridItems.find { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 + if (newParentId == draggedHomeGridItem?.parentId) { + draggedHomeGridItem.left } else { - xIndex + gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 } + } else { + xIndex + } // we are moving an existing home screen item from one place to another if (draggedHomeGridItem != null) { draggedHomeGridItem.apply { + val oldParentId = parentId left = finalXIndex top = yIndex right = finalXIndex @@ -519,8 +539,19 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel docked = yIndex == rowCount - 1 parentId = newParentId + val oldParent = gridItems.firstOrNull { it.id == oldParentId } + val deleteOldParent = if (oldParent?.getFolderItems()?.isEmpty() == true) { + gridItems.remove(oldParent) + true + } else { + false + } + ensureBackgroundThread { context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newParentId, id!!) + if (deleteOldParent && oldParentId != null) { + context.homeScreenGridItemsDB.deleteById(oldParentId) + } } } } else if (draggedItem != null) { @@ -1307,12 +1338,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel ceil(count / 2.0f).roundToInt() } val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() - val centerX = cellXCoords[left] + cellWidth / 2 - val centerY = cellYCoords[top] + cellHeight / 2 + val centerX = cellXCoords[left] + cellWidth / 2 + sideMargins.left + val centerY = cellYCoords[top] + cellHeight / 2 + sideMargins.top val folderDialogWidth = (columnsCount * cellWidth).toFloat() val folderDialogHeight = (rowsCount * cellHeight).toFloat() - val folderDialogTop = centerY - folderDialogHeight / 2 + sideMargins.top - val folderDialogLeft = centerX - folderDialogWidth / 2 + sideMargins.left + val folderDialogTop = centerY - folderDialogHeight / 2 + val folderDialogLeft = centerX - folderDialogWidth / 2 return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight) } From dc6f263e799dc4a7ce754c69da8eb12669af5d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 15:09:56 +0200 Subject: [PATCH 06/24] Ensure that folders never go out of bounds --- .../launcher/views/HomeScreenGrid.kt | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) 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 8716426..0ecb69e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -1332,28 +1332,34 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun HomeScreenGridItem.getFolderRect(): RectF { val count = getFolderItems().count() - val columnsCount = if (count == 2) { - 2 - } else { - ceil(count / 2.0f).roundToInt() - } + val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() val centerX = cellXCoords[left] + cellWidth / 2 + sideMargins.left val centerY = cellYCoords[top] + cellHeight / 2 + sideMargins.top val folderDialogWidth = (columnsCount * cellWidth).toFloat() val folderDialogHeight = (rowsCount * cellHeight).toFloat() - val folderDialogTop = centerY - folderDialogHeight / 2 - val folderDialogLeft = centerX - folderDialogWidth / 2 + var folderDialogTop = centerY - folderDialogHeight / 2 + var folderDialogLeft = centerX - folderDialogWidth / 2 + + if (folderDialogLeft < this@HomeScreenGrid.left + sideMargins.left) { + folderDialogLeft += this@HomeScreenGrid.left + sideMargins.left - folderDialogLeft + } + if (folderDialogLeft + folderDialogWidth > this@HomeScreenGrid.right - sideMargins.right) { + folderDialogLeft -= folderDialogLeft + folderDialogWidth - (this@HomeScreenGrid.right - sideMargins.right) + } + if (folderDialogTop < this@HomeScreenGrid.top + sideMargins.top) { + folderDialogTop += this@HomeScreenGrid.top + sideMargins.top - folderDialogTop + } + if (folderDialogTop + folderDialogHeight > this@HomeScreenGrid.bottom - sideMargins.bottom) { + folderDialogTop -= folderDialogTop + folderDialogHeight - (this@HomeScreenGrid.bottom - sideMargins.bottom) + } + return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight) } private fun HomeScreenGridItem.getPositionInFolder(folder: HomeScreenGridItem): Pair { val count = folder.getFolderItems().count() - val columnsCount = if (count == 2) { - 2 - } else { - ceil(count / 2.0f).roundToInt() - } + val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() val column = left % columnsCount val row = left / columnsCount return Pair(row, column) From dfe9b04f71218421433e034ecde9e087a76b08d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 15:40:47 +0200 Subject: [PATCH 07/24] Add titles to folder views --- .../launcher/activities/MainActivity.kt | 3 +- .../launcher/views/HomeScreenGrid.kt | 69 ++++++++++++++++--- 2 files changed, 62 insertions(+), 10 deletions(-) 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 ddb5d48..f3fad87 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -301,12 +301,13 @@ class MainActivity : SimpleActivity(), FlingListener { newRowCount = config.homeRowCount, newColumnCount = config.homeColumnCount ) + binding.homeScreenGrid.root.updateColors() binding.allAppsFragment.root.onResume() } override fun onStop() { super.onStop() - binding.homeScreenGrid.root.appWidgetHost?.stopListening() + binding.homeScreenGrid.root.appWidgetHost.stopListening() wasJustPaused = false } 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 0ecb69e..a5ea1a7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -22,7 +22,6 @@ import android.view.View import android.widget.RelativeLayout import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable -import androidx.core.graphics.toRectF import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.customview.widget.ExploreByTouchHelper @@ -56,9 +55,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var iconMargin = (context.resources.getDimension(R.dimen.icon_side_margin) * 5 / columnCount).toInt() private var labelSideMargin = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.small_margin).toInt() private var roundedCornerRadius = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.activity_margin) + private var folderPadding = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.medium_margin) private var pageIndicatorRadius = context.resources.getDimension(R.dimen.page_indicator_dot_radius) private var pageIndicatorMargin = context.resources.getDimension(R.dimen.page_indicator_margin) private var textPaint: TextPaint + private var contrastTextPaint: TextPaint + private var folderTitleTextPaint: TextPaint private var dragShadowCirclePaint: Paint private var emptyPageIndicatorPaint: Paint private var currentPageIndicatorPaint: Paint @@ -124,6 +126,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel setShadowLayer(2f, 0f, 0f, Color.BLACK) } + contrastTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getProperTextColor() + textSize = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.smaller_text_size) + setShadowLayer(2f, 0f, 0f, context.getProperTextColor().getContrastColor()) + } + + folderTitleTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getProperTextColor() + textSize = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.medium_text_size) + } + dragShadowCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.resources.getColor(com.simplemobiletools.commons.R.color.hint_white) strokeWidth = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.small_margin) @@ -199,6 +212,13 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + fun updateColors() { + folderTitleTextPaint.color = context.getProperTextColor() + contrastTextPaint.color = context.getProperTextColor() + contrastTextPaint.setShadowLayer(2f, 0f, 0f, context.getProperTextColor().getContrastColor()) + folderBackgroundPaint.color = context.getProperBackgroundColor() + } + fun removeAppIcon(item: HomeScreenGridItem) { ensureBackgroundThread { removeItemFromHomeScreen(item) @@ -862,8 +882,13 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (item.id != draggedItem?.id && item.title.isNotEmpty()) { val textX = baseItemX.toFloat() + labelSideMargin val textY = baseItemY.toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin + val textPaintToUse = if (item.parentId == null) { + textPaint + } else { + contrastTextPaint + } val staticLayout = StaticLayout.Builder - .obtain(item.title, 0, item.title.length, textPaint, cellWidth - 2 * labelSideMargin) + .obtain(item.title, 0, item.title.length, textPaintToUse, cellWidth - 2 * labelSideMargin) .setMaxLines(2) .setEllipsize(TextUtils.TruncateAt.END) .setAlignment(Layout.Alignment.ALIGN_CENTER) @@ -1021,15 +1046,30 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (folder != null) { val items = folder.getFolderItems() val folderRect = folder.getFolderRect() + val folderItemsRect = folder.getFolderItemsRect() canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) + val textX = folderRect.left + folderPadding + val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize + val staticLayout = StaticLayout.Builder + .obtain(folder.title, 0, folder.title.length, folderTitleTextPaint, (folderRect.width() - 2 * folderPadding).toInt()) + .setMaxLines(1) + .setEllipsize(TextUtils.TruncateAt.END) + .setAlignment(Layout.Alignment.ALIGN_CENTER) + .build() + + canvas.save() + canvas.translate(textX, textY) + staticLayout.draw(canvas) + canvas.restore() + items.forEach { item -> val (row, column) = item.getPositionInFolder(folder) handleGridItemDrawing( item, - (folderRect.left + column * cellWidth).roundToInt(), - (folderRect.top + row * cellHeight).roundToInt() + (folderItemsRect.left + column * cellWidth).roundToInt(), + (folderItemsRect.top + row * cellHeight).roundToInt() ) } } @@ -1088,10 +1128,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val clickableLeft: Int val clickableTop: Int if (folder != null && item.parentId == folder.id) { - val folderRect = folder.getFolderRect() + val folderRect = folder.getFolderItemsRect() val (row, column) = item.getPositionInFolder(folder) clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt() - clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() + clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() } else { clickableLeft = cellXCoords[item.left] + sideMargins.left + extraXMargin clickableTop = if (item.docked) { @@ -1328,7 +1368,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun ArrayList.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null } - private fun HomeScreenGridItem.getFolderItems() = gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == id } + private fun HomeScreenGridItem.getFolderItems() = + gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == id } private fun HomeScreenGridItem.getFolderRect(): RectF { val count = getFolderItems().count() @@ -1336,8 +1377,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() val centerX = cellXCoords[left] + cellWidth / 2 + sideMargins.left val centerY = cellYCoords[top] + cellHeight / 2 + sideMargins.top - val folderDialogWidth = (columnsCount * cellWidth).toFloat() - val folderDialogHeight = (rowsCount * cellHeight).toFloat() + val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) + val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize) var folderDialogTop = centerY - folderDialogHeight / 2 var folderDialogLeft = centerX - folderDialogWidth / 2 @@ -1357,6 +1398,16 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight) } + private fun HomeScreenGridItem.getFolderItemsRect(): RectF { + val folderRect = getFolderRect() + return RectF( + folderRect.left + folderPadding, + folderRect.top + folderPadding * 2 + folderTitleTextPaint.textSize, + folderRect.right - folderPadding, + folderRect.bottom - folderPadding + ) + } + private fun HomeScreenGridItem.getPositionInFolder(folder: HomeScreenGridItem): Pair { val count = folder.getFolderItems().count() val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() From 10603be76b648980641392d7358ad4282fb78be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 15:57:32 +0200 Subject: [PATCH 08/24] Properly clean up folder items after removing item from folder --- .../interfaces/HomeScreenGridItemsDao.kt | 3 + .../launcher/views/HomeScreenGrid.kt | 71 ++++++++++--------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt index c7fb8a3..a0bbb2f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt @@ -44,6 +44,9 @@ interface HomeScreenGridItemsDao { @Query("DELETE FROM home_screen_grid_items WHERE parent_id IN (SELECT id FROM home_screen_grid_items WHERE package_name = :packageName)") fun deleteItemsByParentPackageName(packageName: String) + @Query("UPDATE home_screen_grid_items SET `left` = `left` + :shiftBy WHERE parent_id == :folderId AND `left` >= :shiftFrom") + fun shiftFolderItems(folderId: Long, shiftFrom: Int, shiftBy: Int) + @Transaction fun deleteByPackageName(packageName: String) { deleteItemByPackageName(packageName) 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 a5ea1a7..adb54ef 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -551,6 +551,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (draggedHomeGridItem != null) { draggedHomeGridItem.apply { val oldParentId = parentId + val oldLeft = left left = finalXIndex top = yIndex right = finalXIndex @@ -571,6 +572,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newParentId, id!!) if (deleteOldParent && oldParentId != null) { context.homeScreenGridItemsDB.deleteById(oldParentId) + } else if (oldParentId != null) { + gridItems.filter { it.parentId == oldParentId && it.left >= oldLeft }.forEach { + it.left -= 1 + } + context.homeScreenGridItemsDB.shiftFolderItems(oldParentId, oldLeft, -1) } } } @@ -984,6 +990,38 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel canvas.drawCircle(currentIndicatorPosition + pageIndicatorRadius, pageIndicatorY, pageIndicatorRadius, currentPageIndicatorPaint) } + val folder = currentlyOpenFolder + if (folder != null) { + val items = folder.getFolderItems() + val folderRect = folder.getFolderRect() + val folderItemsRect = folder.getFolderItemsRect() + + canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) + + val textX = folderRect.left + folderPadding + val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize + val staticLayout = StaticLayout.Builder + .obtain(folder.title, 0, folder.title.length, folderTitleTextPaint, (folderRect.width() - 2 * folderPadding).toInt()) + .setMaxLines(1) + .setEllipsize(TextUtils.TruncateAt.END) + .setAlignment(Layout.Alignment.ALIGN_CENTER) + .build() + + canvas.save() + canvas.translate(textX, textY) + staticLayout.draw(canvas) + canvas.restore() + + items.forEach { item -> + val (row, column) = item.getPositionInFolder(folder) + handleGridItemDrawing( + item, + (folderItemsRect.left + column * cellWidth).roundToInt(), + (folderItemsRect.top + row * cellHeight).roundToInt() + ) + } + } + if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) { // draw a circle under the current cell @@ -1042,38 +1080,6 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - val folder = currentlyOpenFolder - if (folder != null) { - val items = folder.getFolderItems() - val folderRect = folder.getFolderRect() - val folderItemsRect = folder.getFolderItemsRect() - - canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) - - val textX = folderRect.left + folderPadding - val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize - val staticLayout = StaticLayout.Builder - .obtain(folder.title, 0, folder.title.length, folderTitleTextPaint, (folderRect.width() - 2 * folderPadding).toInt()) - .setMaxLines(1) - .setEllipsize(TextUtils.TruncateAt.END) - .setAlignment(Layout.Alignment.ALIGN_CENTER) - .build() - - canvas.save() - canvas.translate(textX, textY) - staticLayout.draw(canvas) - canvas.restore() - - items.forEach { item -> - val (row, column) = item.getPositionInFolder(folder) - handleGridItemDrawing( - item, - (folderItemsRect.left + column * cellWidth).roundToInt(), - (folderItemsRect.top + row * cellHeight).roundToInt() - ) - } - } - isFirstDraw = false } @@ -1110,6 +1116,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel widgetViews.forEach { it.ignoreTouches = true } + closeFolder(true) } fun fragmentCollapsed() { From 62686f48c37f9f2c249c89ea3898ac4407a92737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 17:09:15 +0200 Subject: [PATCH 09/24] Handle moving items inside a folder --- .../interfaces/HomeScreenGridItemsDao.kt | 4 +- .../launcher/views/HomeScreenGrid.kt | 255 ++++++++++++------ 2 files changed, 171 insertions(+), 88 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt index a0bbb2f..7e4b63e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt @@ -44,8 +44,8 @@ interface HomeScreenGridItemsDao { @Query("DELETE FROM home_screen_grid_items WHERE parent_id IN (SELECT id FROM home_screen_grid_items WHERE package_name = :packageName)") fun deleteItemsByParentPackageName(packageName: String) - @Query("UPDATE home_screen_grid_items SET `left` = `left` + :shiftBy WHERE parent_id == :folderId AND `left` >= :shiftFrom") - fun shiftFolderItems(folderId: Long, shiftFrom: Int, shiftBy: Int) + @Query("UPDATE home_screen_grid_items SET `left` = `left` + :shiftBy WHERE parent_id == :folderId AND `left` > :shiftFrom AND id != :excludingId") + fun shiftFolderItems(folderId: Long, shiftFrom: Int, shiftBy: Int, excludingId: Long) @Transaction fun deleteByPackageName(packageName: String) { 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 adb54ef..3ba7410 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -250,6 +250,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fun itemDraggingStarted(draggedGridItem: HomeScreenGridItem) { draggedItem = draggedGridItem + + if (draggedGridItem.type == ITEM_TYPE_WIDGET) { + closeFolder() + } + if (draggedItem!!.drawable == null) { if (draggedItem?.type == ITEM_TYPE_FOLDER) { draggedItem!!.drawable = @@ -424,14 +429,19 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel // check if the destination cell is empty var isDroppingPositionValid = true val wantedCell = Pair(xIndex, yIndex) - gridItems.filterVisibleOnly().forEach { item -> - for (xCell in item.left..item.right) { - for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { - val cell = Pair(xCell, yCell) - val isAnyCellOccupied = wantedCell == cell - if (isAnyCellOccupied) { - isDroppingPositionValid = false - return@forEach + // No moving folder into the dock + if (draggedHomeGridItem?.type == ITEM_TYPE_FOLDER && yIndex == rowCount - 1) { + isDroppingPositionValid = false + } else { + gridItems.filterVisibleOnly().forEach { item -> + for (xCell in item.left..item.right) { + for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { + val cell = Pair(xCell, yCell) + val isAnyCellOccupied = wantedCell == cell + if (isAnyCellOccupied) { + isDroppingPositionValid = false + return@forEach + } } } } @@ -462,72 +472,92 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun addAppIconOrShortcut() { - val center = gridCenters.minBy { - Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + var isDroppingPositionValid: Boolean = false + var potentialParent: HomeScreenGridItem? = null + var xIndex: Int? = null + var yIndex: Int? = null + var redrawIcons = false + + val folder = currentlyOpenFolder + if (folder != null && folder.getFolderItemsRect().contains( + (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), + (sideMargins.top + draggedItemCurrentCoords.second).toFloat() + ) + ) { + val center = folder.getFolderGridCenters().minBy { + abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) + } + isDroppingPositionValid = true + potentialParent = folder + xIndex = center.first + yIndex = 0 + redrawIcons = true + } else { + val center = gridCenters.minBy { + Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + } + + val gridCells = getClosestGridCells(center) + if (gridCells != null) { + xIndex = gridCells.first + yIndex = gridCells.second + + // check if the destination cell is empty or a folder + isDroppingPositionValid = true + val wantedCell = Pair(xIndex, yIndex) + gridItems.filterVisibleOnly().forEach { item -> + for (xCell in item.left..item.right) { + for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { + val cell = Pair(xCell, yCell) + val isAnyCellOccupied = wantedCell == cell + if (isAnyCellOccupied) { + if (item.type != ITEM_TYPE_WIDGET && !item.docked) { + potentialParent = item + } else { + isDroppingPositionValid = false + } + return@forEach + } + } + } + } + } } - var redrawIcons = false - val gridCells = getClosestGridCells(center) - if (gridCells != null) { - val xIndex = gridCells.first - val yIndex = gridCells.second + if (isDroppingPositionValid) { + val draggedHomeGridItem = gridItems.firstOrNull { it.id == draggedItem?.id } - // check if the destination cell is empty or a folder - var isDroppingPositionValid = true - var potentialParent: HomeScreenGridItem? = null - val wantedCell = Pair(xIndex, yIndex) - gridItems.filterVisibleOnly().forEach { item -> - for (xCell in item.left..item.right) { - for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { - val cell = Pair(xCell, yCell) - val isAnyCellOccupied = wantedCell == cell - if (isAnyCellOccupied) { - if (item.type != ITEM_TYPE_WIDGET) { - potentialParent = item - } else { - isDroppingPositionValid = false - } - return@forEach - } - } - } - } - - if (isDroppingPositionValid) { - val draggedHomeGridItem = gridItems.firstOrNull { it.id == draggedItem?.id } - - if (potentialParent != null) { - if (potentialParent?.type == ITEM_TYPE_FOLDER) { - addAppIconOrShortcut(draggedHomeGridItem, xIndex, yIndex, potentialParent?.id) - } else { - val parentItem = potentialParent!!.copy( - type = ITEM_TYPE_FOLDER, - id = null, - title = resources.getString(com.simplemobiletools.commons.R.string.folder) - ) - ensureBackgroundThread { - val newId = context.homeScreenGridItemsDB.insert(parentItem) - parentItem.id = newId - potentialParent?.apply { - parentId = newId - left = 0 - context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newId, id!!) - } - (context as? MainActivity)?.runOnUiThread { - gridItems.add(parentItem) - addAppIconOrShortcut(draggedHomeGridItem, xIndex, yIndex, newId) - } - } - } - return + if (potentialParent != null) { + if (potentialParent?.type == ITEM_TYPE_FOLDER) { + addAppIconOrShortcut(draggedHomeGridItem, xIndex!!, yIndex!!, potentialParent?.id, toFolderEnd = potentialParent != currentlyOpenFolder) } else { - addAppIconOrShortcut(draggedHomeGridItem, xIndex, yIndex) - return + val parentItem = potentialParent!!.copy( + type = ITEM_TYPE_FOLDER, + id = null, + title = resources.getString(com.simplemobiletools.commons.R.string.folder) + ) + ensureBackgroundThread { + val newId = context.homeScreenGridItemsDB.insert(parentItem) + parentItem.id = newId + potentialParent?.apply { + parentId = newId + left = 0 + context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newId, id!!) + } + (context as? MainActivity)?.runOnUiThread { + gridItems.add(parentItem) + addAppIconOrShortcut(draggedHomeGridItem, xIndex!!, yIndex!!, newId) + } + } } + return } else { - performHapticFeedback() - redrawIcons = true + addAppIconOrShortcut(draggedHomeGridItem, xIndex!!, yIndex!!) + return } + } else { + performHapticFeedback() + redrawIcons = true } draggedItem = null @@ -537,12 +567,24 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - private fun addAppIconOrShortcut(draggedHomeGridItem: HomeScreenGridItem?, xIndex: Int, yIndex: Int, newParentId: Long? = null) { + private fun addAppIconOrShortcut( + draggedHomeGridItem: HomeScreenGridItem?, + xIndex: Int, + yIndex: Int, + newParentId: Long? = null, + toFolderEnd: Boolean = true + ) { val finalXIndex = if (newParentId != null) { - if (newParentId == draggedHomeGridItem?.parentId) { - draggedHomeGridItem.left - } else { + if (toFolderEnd) { gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 + } else { + min(xIndex, gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { + if (draggedHomeGridItem?.parentId == newParentId) { + it.left + } else { + it.left + 1 + } + } ?: 0) } } else { xIndex @@ -572,11 +614,19 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, newParentId, id!!) if (deleteOldParent && oldParentId != null) { context.homeScreenGridItemsDB.deleteById(oldParentId) - } else if (oldParentId != null) { - gridItems.filter { it.parentId == oldParentId && it.left >= oldLeft }.forEach { + } else if (oldParentId != null && gridItems.none { it.parentId == oldParentId && it.left == oldLeft }) { + gridItems.filter { it.parentId == oldParentId && it.left > oldLeft && it.id != id }.forEach { it.left -= 1 } - context.homeScreenGridItemsDB.shiftFolderItems(oldParentId, oldLeft, -1) + context.homeScreenGridItemsDB.shiftFolderItems(oldParentId, oldLeft, -1, id!!) + } + + if (newParentId != null && gridItems.any { it.parentId == newParentId && it.left == left }) { + gridItems.filter { it.parentId == newParentId && it.left >= left && it.id != id }.forEach { + it.left += 1 + } + + context.homeScreenGridItemsDB.shiftFolderItems(newParentId, left - 1, +1, id!!) } } } @@ -1024,21 +1074,37 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) { - // draw a circle under the current cell - val center = gridCenters.minBy { - abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) - } + if (folder != null && folder.getFolderItemsRect().contains( + (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), + (sideMargins.top + draggedItemCurrentCoords.second).toFloat() + ) + ) { - val gridCells = getClosestGridCells(center) - if (gridCells != null) { - val shadowX = cellXCoords[gridCells.first] + iconMargin + iconSize / 2f + extraXMargin + sideMargins.left - val shadowY = if (gridCells.second == rowCount - 1) { - cellYCoords[gridCells.second] + cellHeight - iconMargin - iconSize / 2f - } else { - cellYCoords[gridCells.second] + iconMargin + iconSize / 2f + extraYMargin - } + sideMargins.top + val center = folder.getFolderGridCenters().minBy { + abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) + } + + val shadowX = center.second - cellWidth / 2 + iconMargin + iconSize / 2f + extraXMargin + val shadowY = center.third - cellHeight / 2 + iconMargin + iconSize / 2f + extraYMargin canvas.drawCircle(shadowX, shadowY, iconSize / 2f, dragShadowCirclePaint) + } else { + // draw a circle under the current cell + val center = gridCenters.minBy { + abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + } + + val gridCells = getClosestGridCells(center) + if (gridCells != null) { + val shadowX = cellXCoords[gridCells.first] + iconMargin + iconSize / 2f + extraXMargin + sideMargins.left + val shadowY = if (gridCells.second == rowCount - 1) { + cellYCoords[gridCells.second] + cellHeight - iconMargin - iconSize / 2f + } else { + cellYCoords[gridCells.second] + iconMargin + iconSize / 2f + extraYMargin + } + sideMargins.top + + canvas.drawCircle(shadowX, shadowY, iconSize / 2f, dragShadowCirclePaint) + } } // show the app icon itself at dragging, move it above the finger a bit to make it visible @@ -1415,6 +1481,23 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel ) } + private fun HomeScreenGridItem.getFolderGridCenters(): List> { + val count = getFolderItems().count() + val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() + val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() + val folderItemsRect = getFolderItemsRect() + return (0 until columnsCount * rowsCount) + .toList() + .map { Pair(it % columnsCount, it / columnsCount) } + .mapIndexed { index, (x, y) -> + Triple( + index, + (folderItemsRect.left + x * cellWidth + cellWidth / 2).toInt(), + (folderItemsRect.top + y * cellHeight + cellHeight / 2).toInt() + ) + } + } + private fun HomeScreenGridItem.getPositionInFolder(folder: HomeScreenGridItem): Pair { val count = folder.getFolderItems().count() val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() From c83e7a5226070fc10bc00439530aee2fa9c34a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 18 Aug 2023 17:17:59 +0200 Subject: [PATCH 10/24] Handle dragging items into folders --- .../interfaces/HomeScreenGridItemsDao.kt | 2 +- .../launcher/views/HomeScreenGrid.kt | 37 ++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt index 7e4b63e..1e6fcaf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt @@ -45,7 +45,7 @@ interface HomeScreenGridItemsDao { fun deleteItemsByParentPackageName(packageName: String) @Query("UPDATE home_screen_grid_items SET `left` = `left` + :shiftBy WHERE parent_id == :folderId AND `left` > :shiftFrom AND id != :excludingId") - fun shiftFolderItems(folderId: Long, shiftFrom: Int, shiftBy: Int, excludingId: Long) + fun shiftFolderItems(folderId: Long, shiftFrom: Int, shiftBy: Int, excludingId: Long? = null) @Transaction fun deleteByPackageName(packageName: String) { 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 3ba7410..690a39c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -81,6 +81,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var currentlyOpenFolder: HomeScreenGridItem? = null private var draggingLeftFolderAt: Long? = null + private var draggingEnteredNewFolderAt: Long? = null // apply fake margins at the home screen. Real ones would cause the icons be cut at dragging at screen sides var sideMargins = Rect() @@ -300,6 +301,28 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } draggedItemCurrentCoords = Pair(x, y) + + val center = gridCenters.minBy { + abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + } + val coveredCell = getClosestGridCells(center) + if (coveredCell != null) { + val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.first && it.top == coveredCell.second } + if (coveredFolder != null) { + draggingEnteredNewFolderAt.also { + if (it == null) { + draggingEnteredNewFolderAt = System.currentTimeMillis() + } else if (System.currentTimeMillis() - it > PAGE_CHANGE_HOLD_THRESHOLD) { + openFolder(coveredFolder) + } + } + } else { + draggingEnteredNewFolderAt = null + } + } else { + draggingEnteredNewFolderAt = null + } + if (x > right - sideMargins.right - cellWidth / 2) { doWithPageChangeDelay(PageChangeArea.RIGHT) { nextOrAdditionalPage() @@ -618,7 +641,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel gridItems.filter { it.parentId == oldParentId && it.left > oldLeft && it.id != id }.forEach { it.left -= 1 } - context.homeScreenGridItemsDB.shiftFolderItems(oldParentId, oldLeft, -1, id!!) + context.homeScreenGridItemsDB.shiftFolderItems(oldParentId, oldLeft, -1, id) } if (newParentId != null && gridItems.any { it.parentId == newParentId && it.left == left }) { @@ -626,7 +649,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel it.left += 1 } - context.homeScreenGridItemsDB.shiftFolderItems(newParentId, left - 1, +1, id!!) + context.homeScreenGridItemsDB.shiftFolderItems(newParentId, left - 1, +1, id) } } } @@ -669,6 +692,16 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } } + + ensureBackgroundThread { + if (newParentId != null && gridItems.any { it.parentId == newParentId && it.left == finalXIndex }) { + gridItems.filter { it.parentId == newParentId && it.left >= finalXIndex }.forEach { + it.left += 1 + } + + context.homeScreenGridItemsDB.shiftFolderItems(newParentId, left - 1, +1) + } + } } draggedItem = null From cca2ceaab95cd95760fa3285cfa511789ed52d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Mon, 21 Aug 2023 08:44:46 +0200 Subject: [PATCH 11/24] Add max capacity for folders --- .../launcher/models/HomeScreenGridItem.kt | 4 ++++ .../launcher/views/HomeScreenGrid.kt | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt index 7d2791e..d7ee046 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt @@ -33,6 +33,10 @@ data class HomeScreenGridItem( @Ignore var widthCells: Int = 1, @Ignore var heightCells: Int = 1 ) { + companion object { + const val FOLDER_MAX_CAPACITY = 16 + } + constructor() : this(null, -1, -1, -1, -1, 0, "", "", "", ITEM_TYPE_ICON, "", -1, "", null, false, null, null, null, null, 1, 1) fun getWidthInCells() = if (right == -1 || left == -1) { 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 690a39c..2c91c63 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -313,7 +313,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (it == null) { draggingEnteredNewFolderAt = System.currentTimeMillis() } else if (System.currentTimeMillis() - it > PAGE_CHANGE_HOLD_THRESHOLD) { - openFolder(coveredFolder) + if (coveredFolder.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id) { + performHapticFeedback() + draggingEnteredNewFolderAt = null + } else { + openFolder(coveredFolder) + } } } } else { @@ -597,6 +602,18 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel newParentId: Long? = null, toFolderEnd: Boolean = true ) { + if (newParentId != null && newParentId != draggedHomeGridItem?.parentId) { + gridItems.firstOrNull { it.id == newParentId }?.also { + if (it.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY) { + performHapticFeedback() + draggedItem = null + draggedItemCurrentCoords = Pair(-1, -1) + redrawGrid() + return + } + } + } + val finalXIndex = if (newParentId != null) { if (toFolderEnd) { gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 From a5499a58ada038817df2d00e0508be86745bb479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 22 Aug 2023 12:11:41 +0200 Subject: [PATCH 12/24] Reorder MainActivity --- .../launcher/activities/MainActivity.kt | 240 +++++++++--------- 1 file changed, 120 insertions(+), 120 deletions(-) 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 f3fad87..145e2e8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -73,52 +73,6 @@ class MainActivity : SimpleActivity(), FlingListener { private lateinit var mDetector: GestureDetectorCompat private val binding by viewBinding(ActivityMainBinding::inflate) - 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) { - binding.homeScreenGrid.root.widgetLongPressed(gridItem) - } - - override fun appInfo(gridItem: HomeScreenGridItem) { - launchAppInfo(gridItem.packageName) - } - - override fun remove(gridItem: HomeScreenGridItem) { - binding.homeScreenGrid.root.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) - binding.homeScreenPopupMenuAnchor.y -= yOffset - } - } - companion object { private var mLastUpEvent = 0L private const val ANIMATION_DURATION = 150L @@ -176,80 +130,6 @@ class MainActivity : SimpleActivity(), FlingListener { } } - private fun handleIntentAction(intent: Intent) { - if (intent.action == LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT) { - val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps - val item = launcherApps.getPinItemRequest(intent) - if (item.shortcutInfo == null) { - return - } - - ensureBackgroundThread { - val shortcutId = item.shortcutInfo?.id!! - val label = item.shortcutInfo?.shortLabel?.toString() ?: item.shortcutInfo?.longLabel?.toString() ?: "" - val icon = launcherApps.getShortcutIconDrawable(item.shortcutInfo!!, resources.displayMetrics.densityDpi) - val (page, rect) = findFirstEmptyCell() - val gridItem = HomeScreenGridItem( - null, - rect.left, - rect.top, - rect.right, - rect.bottom, - page, - item.shortcutInfo!!.`package`, - "", - label, - ITEM_TYPE_SHORTCUT, - "", - -1, - shortcutId, - icon.toBitmap(), - false, - null, - icon - ) - - runOnUiThread { - binding.homeScreenGrid.root.skipToPage(page) - } - // delay showing the shortcut both to let the user see adding it in realtime and hackily avoid concurrent modification exception at HomeScreenGrid - Thread.sleep(2000) - - try { - item.accept() - binding.homeScreenGrid.root.storeAndShowGridItem(gridItem) - } catch (ignored: IllegalStateException) { - } - } - } - } - - private fun findFirstEmptyCell(): Pair { - val gridItems = homeScreenGridItemsDB.getAllItems() as ArrayList - val maxPage = gridItems.map { it.page }.max() - val occupiedCells = ArrayList>() - gridItems.forEach { item -> - for (xCell in item.left..item.right) { - for (yCell in item.top..item.bottom) { - occupiedCells.add(Triple(item.page, xCell, yCell)) - } - } - } - - for (page in 0 until maxPage) { - for (checkedYCell in 0 until config.homeColumnCount) { - for (checkedXCell in 0 until config.homeRowCount - 1) { - val wantedCell = Triple(page, checkedXCell, checkedYCell) - if (!occupiedCells.contains(wantedCell)) { - return Pair(page, Rect(wantedCell.second, wantedCell.third, wantedCell.second, wantedCell.third)) - } - } - } - } - - return Pair(maxPage + 1, Rect(0, 0, 0, 0)) - } - override fun onStart() { super.onStart() binding.homeScreenGrid.root.appWidgetHost.startListening() @@ -452,6 +332,79 @@ class MainActivity : SimpleActivity(), FlingListener { return true } + private fun handleIntentAction(intent: Intent) { + if (intent.action == LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT) { + val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + val item = launcherApps.getPinItemRequest(intent) + if (item.shortcutInfo == null) { + return + } + + ensureBackgroundThread { + val shortcutId = item.shortcutInfo?.id!! + val label = item.shortcutInfo?.shortLabel?.toString() ?: item.shortcutInfo?.longLabel?.toString() ?: "" + val icon = launcherApps.getShortcutIconDrawable(item.shortcutInfo!!, resources.displayMetrics.densityDpi) + val (page, rect) = findFirstEmptyCell() + val gridItem = HomeScreenGridItem( + null, + rect.left, + rect.top, + rect.right, + rect.bottom, + page, + item.shortcutInfo!!.`package`, + "", + label, + ITEM_TYPE_SHORTCUT, + "", + -1, + shortcutId, + icon.toBitmap(), + false, + null, + icon + ) + + runOnUiThread { + binding.homeScreenGrid.root.skipToPage(page) + } + // delay showing the shortcut both to let the user see adding it in realtime and hackily avoid concurrent modification exception at HomeScreenGrid + Thread.sleep(2000) + + try { + item.accept() + binding.homeScreenGrid.root.storeAndShowGridItem(gridItem) + } catch (ignored: IllegalStateException) { + } + } + } + } + + private fun findFirstEmptyCell(): Pair { + val gridItems = homeScreenGridItemsDB.getAllItems() as ArrayList + val maxPage = gridItems.map { it.page }.max() + val occupiedCells = ArrayList>() + gridItems.forEach { item -> + for (xCell in item.left..item.right) { + for (yCell in item.top..item.bottom) { + occupiedCells.add(Triple(item.page, xCell, yCell)) + } + } + } + + for (page in 0 until maxPage) { + for (checkedYCell in 0 until config.homeColumnCount) { + for (checkedXCell in 0 until config.homeRowCount - 1) { + val wantedCell = Triple(page, checkedXCell, checkedYCell) + if (!occupiedCells.contains(wantedCell)) { + return Pair(page, Rect(wantedCell.second, wantedCell.third, wantedCell.second, wantedCell.third)) + } + } + } + } + + return Pair(maxPage + 1, Rect(0, 0, 0, 0)) + } // 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 && @@ -708,6 +661,53 @@ class MainActivity : SimpleActivity(), FlingListener { } } + 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) { + binding.homeScreenGrid.root.widgetLongPressed(gridItem) + } + + override fun appInfo(gridItem: HomeScreenGridItem) { + launchAppInfo(gridItem.packageName) + } + + override fun remove(gridItem: HomeScreenGridItem) { + binding.homeScreenGrid.root.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) + binding.homeScreenPopupMenuAnchor.y -= yOffset + } + } + + private class MyGestureListener(private val flingListener: FlingListener) : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapUp(event: MotionEvent): Boolean { (flingListener as MainActivity).homeScreenClicked(event.x, event.y) From 516f61fe907f3478fd282ac9b460ab68c40192b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 22 Aug 2023 13:59:21 +0200 Subject: [PATCH 13/24] Generate folder icon from icons inside of it --- .../launcher/views/HomeScreenGrid.kt | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) 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 2c91c63..4fd3d3c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -10,6 +10,7 @@ import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.graphics.* import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable import android.os.Bundle import android.text.Layout import android.text.StaticLayout @@ -27,8 +28,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.customview.widget.ExploreByTouchHelper import com.google.android.material.math.MathUtils import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.ensureBackgroundThread -import com.simplemobiletools.commons.helpers.isSPlus +import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.launcher.R import com.simplemobiletools.launcher.activities.MainActivity import com.simplemobiletools.launcher.databinding.HomeScreenGridBinding @@ -65,6 +65,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var emptyPageIndicatorPaint: Paint private var currentPageIndicatorPaint: Paint private var folderBackgroundPaint: Paint + private var folderIconBackgroundPaint: Paint + private var folderIconBorderPaint: Paint private var draggedItem: HomeScreenGridItem? = null private var resizedWidget: HomeScreenGridItem? = null private var isFirstDraw = true @@ -157,6 +159,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel style = Paint.Style.FILL } + folderIconBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getProperBackgroundColor().adjustAlpha(MEDIUM_ALPHA) + style = Paint.Style.FILL + } + + folderIconBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getProperBackgroundColor().adjustAlpha(HIGHER_ALPHA) + strokeWidth = context.resources.getDimension(R.dimen.page_indicator_stroke_width) * 5 + style = Paint.Style.STROKE + } + val sideMargin = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.normal_margin).toInt() sideMargins.apply { top = context.statusBarHeight @@ -181,8 +194,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (item.type == ITEM_TYPE_ICON) { item.drawable = context.getDrawableForPackageName(item.packageName) } else if (item.type == ITEM_TYPE_FOLDER) { - item.drawable = - resources.getColoredDrawableWithColor(com.simplemobiletools.commons.R.drawable.ic_folder_vector, context.getProperPrimaryColor()) + item.drawable = item.generateFolderDrawable() } else if (item.type == ITEM_TYPE_SHORTCUT) { if (item.icon != null) { item.drawable = BitmapDrawable(item.icon) @@ -258,8 +270,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (draggedItem!!.drawable == null) { if (draggedItem?.type == ITEM_TYPE_FOLDER) { - draggedItem!!.drawable = - resources.getColoredDrawableWithColor(com.simplemobiletools.commons.R.drawable.ic_folder_vector, context.getProperPrimaryColor()) + draggedItem!!.drawable = draggedGridItem!!.generateFolderDrawable() } else { draggedItem!!.drawable = context.getDrawableForPackageName(draggedGridItem.packageName) } @@ -972,7 +983,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val drawableX = baseItemX + iconMargin + extraXMargin val drawable = if (item.type == ITEM_TYPE_FOLDER) { - resources.getColoredDrawableWithColor(com.simplemobiletools.commons.R.drawable.ic_folder_vector, context.getProperPrimaryColor()) + item.generateFolderDrawable() } else { item.drawable!! } @@ -980,10 +991,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (item.docked) { val drawableY = cellYCoords[rowCount - 1] + cellHeight - iconMargin - iconSize + sideMargins.top - drawable.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) + drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) } else { val drawableY = baseItemY + iconMargin + extraYMargin - drawable.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) + drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) if (item.id != draggedItem?.id && item.title.isNotEmpty()) { val textX = baseItemX.toFloat() + labelSideMargin @@ -1007,7 +1018,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - drawable.draw(canvas) + drawable?.draw(canvas) } } @@ -1494,6 +1505,35 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun HomeScreenGridItem.getFolderItems() = gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == id } + private fun HomeScreenGridItem.generateFolderDrawable(): Drawable? { + if (iconSize == 0) { + return null + } + + val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val circlePath = Path().apply { addCircle((iconSize / 2).toFloat(), (iconSize / 2).toFloat(), (iconSize / 2).toFloat(), Path.Direction.CCW) } + canvas.clipPath(circlePath) + canvas.drawPaint(folderIconBackgroundPaint) + val items = getFolderItems() + val itemsCount = getFolderItems().count() + val folderColumnCount = ceil(sqrt(itemsCount.toDouble())).roundToInt() + val folderRowCount = ceil(itemsCount.toFloat() / folderColumnCount).roundToInt() + val scaledCellSize = (iconSize.toFloat() / folderColumnCount) + val scaledGap = scaledCellSize / 5f + val scaledIconSize = (iconSize - (folderColumnCount + 1) * scaledGap) / folderColumnCount + val extraYMargin = if (folderRowCount < folderColumnCount) scaledIconSize / 2 else 0f + items.forEach { + val (row, column) = it.getPositionInFolder(this@generateFolderDrawable) + val drawableX = (scaledGap + column * scaledIconSize + column * scaledGap).toInt() + val drawableY = (extraYMargin + scaledGap + row * scaledIconSize + row * scaledGap).toInt() + it.drawable?.setBounds(drawableX, drawableY, drawableX + scaledIconSize.toInt(), drawableY + scaledIconSize.toInt()) + it.drawable?.draw(canvas) + } + canvas.drawPath(circlePath, folderIconBorderPaint) + return BitmapDrawable(resources, bitmap) + } + private fun HomeScreenGridItem.getFolderRect(): RectF { val count = getFolderItems().count() val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() From 81dff807b41d57a7404f9a01ba8825cf11118c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 22 Aug 2023 14:31:57 +0200 Subject: [PATCH 14/24] Prevent opening folder while dragging it over its old spot --- .../simplemobiletools/launcher/views/HomeScreenGrid.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 4fd3d3c..bf6bf95 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -291,7 +291,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel draggingLeftFolderAt.also { if (it == null) { draggingLeftFolderAt = System.currentTimeMillis() - } else if (System.currentTimeMillis() - it > PAGE_CHANGE_HOLD_THRESHOLD) { + } else if (System.currentTimeMillis() - it > FOLDER_CLOSE_HOLD_THRESHOLD) { closeFolder() } } @@ -319,11 +319,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val coveredCell = getClosestGridCells(center) if (coveredCell != null) { val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.first && it.top == coveredCell.second } - if (coveredFolder != null) { + if (coveredFolder != null && coveredFolder.id != draggedItem?.id) { draggingEnteredNewFolderAt.also { if (it == null) { draggingEnteredNewFolderAt = System.currentTimeMillis() - } else if (System.currentTimeMillis() - it > PAGE_CHANGE_HOLD_THRESHOLD) { + } else if (System.currentTimeMillis() - it > FOLDER_OPEN_HOLD_THRESHOLD) { if (coveredFolder.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id) { performHapticFeedback() draggingEnteredNewFolderAt = null @@ -1599,6 +1599,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel companion object { private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L + private const val FOLDER_OPEN_HOLD_THRESHOLD = 500L + private const val FOLDER_CLOSE_HOLD_THRESHOLD = 300L private enum class PageChangeArea { LEFT, From a7ffb6328a8365c50420d8f871df54d7a121f627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 22 Aug 2023 14:43:03 +0200 Subject: [PATCH 15/24] Prevent drawing empty folders --- .../com/simplemobiletools/launcher/views/HomeScreenGrid.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 bf6bf95..5ec585d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -1536,8 +1536,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun HomeScreenGridItem.getFolderRect(): RectF { val count = getFolderItems().count() - val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() - val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() + if (count == 0) { + return RectF(0f, 0f, 0f, 0f) + } + val columnsCount = ceil(sqrt(count.toDouble())).toInt() + val rowsCount = ceil(count.toFloat() / columnsCount).toInt() val centerX = cellXCoords[left] + cellWidth / 2 + sideMargins.left val centerY = cellYCoords[top] + cellHeight / 2 + sideMargins.top val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) From 7a5c698322b645620179d4827ac0977cf0c5c680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Tue, 22 Aug 2023 14:45:19 +0200 Subject: [PATCH 16/24] Fix vertical centering of icons in folder icon --- .../com/simplemobiletools/launcher/views/HomeScreenGrid.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5ec585d..7b68577 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -1522,7 +1522,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val scaledCellSize = (iconSize.toFloat() / folderColumnCount) val scaledGap = scaledCellSize / 5f val scaledIconSize = (iconSize - (folderColumnCount + 1) * scaledGap) / folderColumnCount - val extraYMargin = if (folderRowCount < folderColumnCount) scaledIconSize / 2 else 0f + val extraYMargin = if (folderRowCount < folderColumnCount) (scaledIconSize + scaledGap) / 2 else 0f items.forEach { val (row, column) = it.getPositionInFolder(this@generateFolderDrawable) val drawableX = (scaledGap + column * scaledIconSize + column * scaledGap).toInt() From 3cc00157ccc8a215c27a7416eb39fdf4842f75fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Wed, 23 Aug 2023 16:48:56 +0200 Subject: [PATCH 17/24] Add folder opening and closing animation --- .../launcher/views/HomeScreenGrid.kt | 359 +++++++++++------- 1 file changed, 221 insertions(+), 138 deletions(-) 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 7b68577..07b4e15 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -20,9 +20,11 @@ import android.util.AttributeSet import android.util.Size import android.util.SizeF import android.view.View +import android.view.animation.DecelerateInterpolator import android.widget.RelativeLayout import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable +import androidx.core.graphics.withScale import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.customview.widget.ExploreByTouchHelper @@ -81,7 +83,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private var pageChangeEnabled = true private var pageChangeIndicatorsAlpha = 0f - private var currentlyOpenFolder: HomeScreenGridItem? = null + private var currentlyOpenFolder: HomeScreenFolder? = null private var draggingLeftFolderAt: Long? = null private var draggingEnteredNewFolderAt: Long? = null @@ -194,7 +196,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (item.type == ITEM_TYPE_ICON) { item.drawable = context.getDrawableForPackageName(item.packageName) } else if (item.type == ITEM_TYPE_FOLDER) { - item.drawable = item.generateFolderDrawable() + item.drawable = item.toFolder().generateDrawable() } else if (item.type == ITEM_TYPE_SHORTCUT) { if (item.icon != null) { item.drawable = BitmapDrawable(item.icon) @@ -270,7 +272,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (draggedItem!!.drawable == null) { if (draggedItem?.type == ITEM_TYPE_FOLDER) { - draggedItem!!.drawable = draggedGridItem!!.generateFolderDrawable() + draggedItem!!.drawable = draggedGridItem.toFolder().generateDrawable() } else { draggedItem!!.drawable = context.getDrawableForPackageName(draggedGridItem.packageName) } @@ -285,7 +287,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } currentlyOpenFolder?.also { folder -> - if (folder.getFolderRect().contains(x.toFloat(), y.toFloat())) { + if (folder.getDrawingRect().contains(x.toFloat(), y.toFloat())) { draggingLeftFolderAt = null } else { draggingLeftFolderAt.also { @@ -324,7 +326,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (it == null) { draggingEnteredNewFolderAt = System.currentTimeMillis() } else if (System.currentTimeMillis() - it > FOLDER_OPEN_HOLD_THRESHOLD) { - if (coveredFolder.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id) { + if (coveredFolder.toFolder().getItems() + .count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id + ) { performHapticFeedback() draggingEnteredNewFolderAt = null } else { @@ -518,16 +522,16 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel var redrawIcons = false val folder = currentlyOpenFolder - if (folder != null && folder.getFolderItemsRect().contains( + if (folder != null && folder.getItemsDrawingRect().contains( (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), (sideMargins.top + draggedItemCurrentCoords.second).toFloat() ) ) { - val center = folder.getFolderGridCenters().minBy { + val center = folder.getItemsGridCenters().minBy { abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) } isDroppingPositionValid = true - potentialParent = folder + potentialParent = folder.item xIndex = center.first yIndex = 0 redrawIcons = true @@ -568,7 +572,13 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (potentialParent != null) { if (potentialParent?.type == ITEM_TYPE_FOLDER) { - addAppIconOrShortcut(draggedHomeGridItem, xIndex!!, yIndex!!, potentialParent?.id, toFolderEnd = potentialParent != currentlyOpenFolder) + addAppIconOrShortcut( + draggedHomeGridItem, + xIndex!!, + yIndex!!, + potentialParent?.id, + toFolderEnd = potentialParent != currentlyOpenFolder?.item + ) } else { val parentItem = potentialParent!!.copy( type = ITEM_TYPE_FOLDER, @@ -615,7 +625,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel ) { if (newParentId != null && newParentId != draggedHomeGridItem?.parentId) { gridItems.firstOrNull { it.id == newParentId }?.also { - if (it.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY) { + if (it.toFolder().getItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY) { performHapticFeedback() draggedItem = null draggedItemCurrentCoords = Pair(-1, -1) @@ -627,9 +637,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val finalXIndex = if (newParentId != null) { if (toFolderEnd) { - gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 + gridItems.firstOrNull { it.id == newParentId }?.toFolder()?.getItems()?.maxOf { it.left + 1 } ?: 0 } else { - min(xIndex, gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { + min(xIndex, gridItems.firstOrNull { it.id == newParentId }?.toFolder()?.getItems()?.maxOf { if (draggedHomeGridItem?.parentId == newParentId) { it.left } else { @@ -654,7 +664,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel parentId = newParentId val oldParent = gridItems.firstOrNull { it.id == oldParentId } - val deleteOldParent = if (oldParent?.getFolderItems()?.isEmpty() == true) { + val deleteOldParent = if (oldParent?.toFolder()?.getItems()?.isEmpty() == true) { gridItems.remove(oldParent) true } else { @@ -983,7 +993,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val drawableX = baseItemX + iconMargin + extraXMargin val drawable = if (item.type == ITEM_TYPE_FOLDER) { - item.generateFolderDrawable() + item.toFolder().generateDrawable() } else { item.drawable!! } @@ -1103,45 +1113,53 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val folder = currentlyOpenFolder if (folder != null) { - val items = folder.getFolderItems() - val folderRect = folder.getFolderRect() - val folderItemsRect = folder.getFolderItemsRect() + val items = folder.getItems() + val folderRect = folder.getDrawingRect() + val folderItemsRect = folder.getItemsDrawingRect() canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) - val textX = folderRect.left + folderPadding - val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize - val staticLayout = StaticLayout.Builder - .obtain(folder.title, 0, folder.title.length, folderTitleTextPaint, (folderRect.width() - 2 * folderPadding).toInt()) - .setMaxLines(1) - .setEllipsize(TextUtils.TruncateAt.END) - .setAlignment(Layout.Alignment.ALIGN_CENTER) - .build() + canvas.withScale(folder.scale, folder.scale, folderRect.centerX(), folderRect.centerY()) { + val textX = folderRect.left + folderPadding + val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize + val staticLayout = StaticLayout.Builder + .obtain( + folder.item.title, + 0, + folder.item.title.length, + folderTitleTextPaint, + (folderRect.width() - 2 * folderPadding * folder.scale).toInt() + ) + .setMaxLines(1) + .setEllipsize(TextUtils.TruncateAt.END) + .setAlignment(Layout.Alignment.ALIGN_CENTER) + .build() - canvas.save() - canvas.translate(textX, textY) - staticLayout.draw(canvas) - canvas.restore() + canvas.save() + canvas.translate(textX, textY) + staticLayout.draw(canvas) + canvas.restore() - items.forEach { item -> - val (row, column) = item.getPositionInFolder(folder) - handleGridItemDrawing( - item, - (folderItemsRect.left + column * cellWidth).roundToInt(), - (folderItemsRect.top + row * cellHeight).roundToInt() - ) + items.forEach { item -> + val (row, column) = folder.getItemPosition(item) + handleGridItemDrawing( + item, + (folderItemsRect.left + column * cellWidth).roundToInt(), + (folderItemsRect.top + row * cellHeight).roundToInt() + ) + } } } if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) { - if (folder != null && folder.getFolderItemsRect().contains( + if (folder != null && folder.getItemsDrawingRect().contains( (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), (sideMargins.top + draggedItemCurrentCoords.second).toFloat() ) ) { - val center = folder.getFolderGridCenters().minBy { + val center = folder.getItemsGridCenters().minBy { abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) } @@ -1261,9 +1279,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val folder = currentlyOpenFolder val clickableLeft: Int val clickableTop: Int - if (folder != null && item.parentId == folder.id) { - val folderRect = folder.getFolderItemsRect() - val (row, column) = item.getPositionInFolder(folder) + if (folder != null && item.parentId == folder.item.id) { + val folderRect = folder.getItemsDrawingRect() + val (row, column) = folder.getItemPosition(item) clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt() clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() } else { @@ -1305,7 +1323,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fun isClickingGridItem(x: Int, y: Int): HomeScreenGridItem? { currentlyOpenFolder?.also { folder -> - folder.getFolderItems().forEach { gridItem -> + folder.getItems().forEach { gridItem -> val rect = getClickableRect(gridItem) if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { return gridItem @@ -1454,14 +1472,20 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } fun openFolder(folder: HomeScreenGridItem) { - currentlyOpenFolder = folder - redrawGrid() + if (currentlyOpenFolder == null) { + currentlyOpenFolder = folder.toFolder(animateOpening = true) + redrawGrid() + } else { + closeFolder() + } } fun closeFolder(redraw: Boolean = false) { - currentlyOpenFolder = null - if (redraw) { - redrawGrid() + currentlyOpenFolder?.animateClosing { + currentlyOpenFolder = null + if (redraw) { + redrawGrid() + } } } @@ -1502,101 +1526,158 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun ArrayList.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null } - private fun HomeScreenGridItem.getFolderItems() = - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == id } + private fun HomeScreenGridItem.toFolder(animateOpening: Boolean = false) = HomeScreenFolder(this, animateOpening) - private fun HomeScreenGridItem.generateFolderDrawable(): Drawable? { - if (iconSize == 0) { - return null - } + private inner class HomeScreenFolder( + val item: HomeScreenGridItem, + animateOpening: Boolean + ) { + var scale: Float = 1f - val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - val circlePath = Path().apply { addCircle((iconSize / 2).toFloat(), (iconSize / 2).toFloat(), (iconSize / 2).toFloat(), Path.Direction.CCW) } - canvas.clipPath(circlePath) - canvas.drawPaint(folderIconBackgroundPaint) - val items = getFolderItems() - val itemsCount = getFolderItems().count() - val folderColumnCount = ceil(sqrt(itemsCount.toDouble())).roundToInt() - val folderRowCount = ceil(itemsCount.toFloat() / folderColumnCount).roundToInt() - val scaledCellSize = (iconSize.toFloat() / folderColumnCount) - val scaledGap = scaledCellSize / 5f - val scaledIconSize = (iconSize - (folderColumnCount + 1) * scaledGap) / folderColumnCount - val extraYMargin = if (folderRowCount < folderColumnCount) (scaledIconSize + scaledGap) / 2 else 0f - items.forEach { - val (row, column) = it.getPositionInFolder(this@generateFolderDrawable) - val drawableX = (scaledGap + column * scaledIconSize + column * scaledGap).toInt() - val drawableY = (extraYMargin + scaledGap + row * scaledIconSize + row * scaledGap).toInt() - it.drawable?.setBounds(drawableX, drawableY, drawableX + scaledIconSize.toInt(), drawableY + scaledIconSize.toInt()) - it.drawable?.draw(canvas) - } - canvas.drawPath(circlePath, folderIconBorderPaint) - return BitmapDrawable(resources, bitmap) - } - - private fun HomeScreenGridItem.getFolderRect(): RectF { - val count = getFolderItems().count() - if (count == 0) { - return RectF(0f, 0f, 0f, 0f) - } - val columnsCount = ceil(sqrt(count.toDouble())).toInt() - val rowsCount = ceil(count.toFloat() / columnsCount).toInt() - val centerX = cellXCoords[left] + cellWidth / 2 + sideMargins.left - val centerY = cellYCoords[top] + cellHeight / 2 + sideMargins.top - val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) - val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize) - var folderDialogTop = centerY - folderDialogHeight / 2 - var folderDialogLeft = centerX - folderDialogWidth / 2 - - if (folderDialogLeft < this@HomeScreenGrid.left + sideMargins.left) { - folderDialogLeft += this@HomeScreenGrid.left + sideMargins.left - folderDialogLeft - } - if (folderDialogLeft + folderDialogWidth > this@HomeScreenGrid.right - sideMargins.right) { - folderDialogLeft -= folderDialogLeft + folderDialogWidth - (this@HomeScreenGrid.right - sideMargins.right) - } - if (folderDialogTop < this@HomeScreenGrid.top + sideMargins.top) { - folderDialogTop += this@HomeScreenGrid.top + sideMargins.top - folderDialogTop - } - if (folderDialogTop + folderDialogHeight > this@HomeScreenGrid.bottom - sideMargins.bottom) { - folderDialogTop -= folderDialogTop + folderDialogHeight - (this@HomeScreenGrid.bottom - sideMargins.bottom) - } - - return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight) - } - - private fun HomeScreenGridItem.getFolderItemsRect(): RectF { - val folderRect = getFolderRect() - return RectF( - folderRect.left + folderPadding, - folderRect.top + folderPadding * 2 + folderTitleTextPaint.textSize, - folderRect.right - folderPadding, - folderRect.bottom - folderPadding - ) - } - - private fun HomeScreenGridItem.getFolderGridCenters(): List> { - val count = getFolderItems().count() - val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() - val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() - val folderItemsRect = getFolderItemsRect() - return (0 until columnsCount * rowsCount) - .toList() - .map { Pair(it % columnsCount, it / columnsCount) } - .mapIndexed { index, (x, y) -> - Triple( - index, - (folderItemsRect.left + x * cellWidth + cellWidth / 2).toInt(), - (folderItemsRect.top + y * cellHeight + cellHeight / 2).toInt() - ) + init { + if (animateOpening) { + scale = 0f + post { + ValueAnimator.ofFloat(0f, 1f) + .apply { + interpolator = DecelerateInterpolator() + addUpdateListener { + scale = it.animatedValue as Float + redrawGrid() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + scale = 1f + redrawGrid() + } + }) + duration = FOLDER_ANIMATION_DURATION + start() + } + } } - } + } - private fun HomeScreenGridItem.getPositionInFolder(folder: HomeScreenGridItem): Pair { - val count = folder.getFolderItems().count() - val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() - val column = left % columnsCount - val row = left / columnsCount - return Pair(row, column) + fun getItems() = + gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == item.id } + + fun generateDrawable(): Drawable? { + if (iconSize == 0) { + return null + } + + val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + val circlePath = Path().apply { addCircle((iconSize / 2).toFloat(), (iconSize / 2).toFloat(), (iconSize / 2).toFloat(), Path.Direction.CCW) } + canvas.clipPath(circlePath) + canvas.drawPaint(folderIconBackgroundPaint) + val items = getItems() + val itemsCount = getItems().count() + val folderColumnCount = ceil(sqrt(itemsCount.toDouble())).roundToInt() + val folderRowCount = ceil(itemsCount.toFloat() / folderColumnCount).roundToInt() + val scaledCellSize = (iconSize.toFloat() / folderColumnCount) + val scaledGap = scaledCellSize / 5f + val scaledIconSize = (iconSize - (folderColumnCount + 1) * scaledGap) / folderColumnCount + val extraYMargin = if (folderRowCount < folderColumnCount) (scaledIconSize + scaledGap) / 2 else 0f + items.forEach { + val (row, column) = getItemPosition(it) + val drawableX = (scaledGap + column * scaledIconSize + column * scaledGap).toInt() + val drawableY = (extraYMargin + scaledGap + row * scaledIconSize + row * scaledGap).toInt() + it.drawable?.setBounds(drawableX, drawableY, drawableX + scaledIconSize.toInt(), drawableY + scaledIconSize.toInt()) + it.drawable?.draw(canvas) + } + canvas.drawPath(circlePath, folderIconBorderPaint) + return BitmapDrawable(resources, bitmap) + } + + fun getDrawingRect(overrideScale: Float? = null): RectF { + val finalScale = overrideScale ?: scale + val count = getItems().count() + if (count == 0) { + return RectF(0f, 0f, 0f, 0f) + } + val columnsCount = ceil(sqrt(count.toDouble())).toInt() + val rowsCount = ceil(count.toFloat() / columnsCount).toInt() + val centerX = cellXCoords[item.left] + cellWidth / 2 + sideMargins.left + val centerY = cellYCoords[item.top] + cellHeight / 2 + sideMargins.top + val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) * finalScale + val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize) * finalScale + var folderDialogTop = centerY - folderDialogHeight / 2 + var folderDialogLeft = centerX - folderDialogWidth / 2 + + if (folderDialogLeft < left + sideMargins.left) { + folderDialogLeft += left + sideMargins.left - folderDialogLeft + } + if (folderDialogLeft + folderDialogWidth > right - sideMargins.right) { + folderDialogLeft -= folderDialogLeft + folderDialogWidth - (right - sideMargins.right) + } + if (folderDialogTop < top + sideMargins.top) { + folderDialogTop += top + sideMargins.top - folderDialogTop + } + if (folderDialogTop + folderDialogHeight > bottom - sideMargins.bottom) { + folderDialogTop -= folderDialogTop + folderDialogHeight - (bottom - sideMargins.bottom) + } + + return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight) + } + + fun getItemsDrawingRect(): RectF { + val folderRect = getDrawingRect(overrideScale = 1f) + return RectF( + folderRect.left + folderPadding, + folderRect.top + folderPadding * 2 + folderTitleTextPaint.textSize, + folderRect.right - folderPadding, + folderRect.bottom - folderPadding + ) + } + + fun getItemsGridCenters(): List> { + val count = getItems().count() + val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() + val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() + val folderItemsRect = getItemsDrawingRect() + return (0 until columnsCount * rowsCount) + .toList() + .map { Pair(it % columnsCount, it / columnsCount) } + .mapIndexed { index, (x, y) -> + Triple( + index, + (folderItemsRect.left + x * cellWidth + cellWidth / 2).toInt(), + (folderItemsRect.top + y * cellHeight + cellHeight / 2).toInt() + ) + } + } + + fun getItemPosition(item: HomeScreenGridItem): Pair { + val count = getItems().count() + val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() + val column = item.left % columnsCount + val row = item.left / columnsCount + return Pair(row, column) + } + + fun animateClosing(callback: () -> Unit) { + post { + ValueAnimator.ofFloat(scale, 0.2f) + .apply { + interpolator = DecelerateInterpolator() + addUpdateListener { + scale = it.animatedValue as Float + redrawGrid() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + scale = 0.2f + callback() + } + }) + duration = (FOLDER_ANIMATION_DURATION * (max(0f, scale - 0.2f))).toLong() + start() + } + } + } } companion object { @@ -1604,6 +1685,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L private const val FOLDER_OPEN_HOLD_THRESHOLD = 500L private const val FOLDER_CLOSE_HOLD_THRESHOLD = 300L + private const val FOLDER_ANIMATION_DURATION = 200L private enum class PageChangeArea { LEFT, @@ -1612,3 +1694,4 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } } + From 1863a9e6a9c6a1beeb2c29758ff87e451f68cb61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Thu, 24 Aug 2023 12:49:13 +0200 Subject: [PATCH 18/24] Simplify drawing and cells logic --- .../launcher/models/HomeScreenGridItem.kt | 3 + .../launcher/views/HomeScreenGrid.kt | 291 +++++++++--------- 2 files changed, 153 insertions(+), 141 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt index d7ee046..8071ddb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.launcher.models import android.appwidget.AppWidgetProviderInfo import android.content.pm.ActivityInfo import android.graphics.Bitmap +import android.graphics.Point import android.graphics.drawable.Drawable import androidx.room.* import com.simplemobiletools.launcher.helpers.ITEM_TYPE_ICON @@ -68,4 +69,6 @@ data class HomeScreenGridItem( } fun getItemIdentifier() = "$packageName/$activityName" + + fun getTopLeft() = Point(left, top) } 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 067f476..51fddfc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -26,6 +26,7 @@ import android.widget.RelativeLayout import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.withScale +import androidx.core.graphics.withTranslation import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.customview.widget.ExploreByTouchHelper @@ -48,8 +49,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private lateinit var binding: HomeScreenGridBinding private var columnCount = context.config.homeColumnCount private var rowCount = context.config.homeRowCount - private var cellXCoords = ArrayList(columnCount) - private var cellYCoords = ArrayList(rowCount) + private var dockTop = 0 + private val cells = mutableMapOf() var cellWidth = 0 var cellHeight = 0 private var extraXMargin = 0 @@ -94,7 +95,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel var sideMargins = Rect() private var gridItems = ArrayList() - private var gridCenters = ArrayList>() + private var gridCenters = ArrayList() private var draggedItemCurrentCoords = Pair(-1, -1) private var widgetViews = ArrayList() @@ -201,8 +202,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (columnCount != newColumnCount || rowCount != newRowCount) { rowCount = newRowCount columnCount = newColumnCount - cellXCoords = ArrayList(columnCount) - cellYCoords = ArrayList(rowCount) + cells.clear() gridCenters.clear() iconMargin = (context.resources.getDimension(R.dimen.icon_side_margin) * 5 / columnCount).toInt() redrawWidgets = true @@ -297,11 +297,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel draggedItemCurrentCoords = Pair(x, y) val center = gridCenters.minBy { - abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) } val coveredCell = getClosestGridCells(center) if (coveredCell != null) { - val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.first && it.top == coveredCell.second } + val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.x && it.top == coveredCell.y } if (coveredFolder != null && coveredFolder.id != draggedItem?.id) { draggingEnteredNewFolderAt.also { if (it == null) { @@ -404,14 +404,14 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun moveItem() { val draggedHomeGridItem = gridItems.firstOrNull { it.id == draggedItem?.id } val center = gridCenters.minBy { - abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) } var redrawIcons = false val gridCells = getClosestGridCells(center) if (gridCells != null) { - val xIndex = gridCells.first - val yIndex = gridCells.second + val xIndex = gridCells.x + val yIndex = gridCells.y // check if the destination cell is empty var isDroppingPositionValid = true @@ -420,7 +420,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (draggedHomeGridItem?.type == ITEM_TYPE_FOLDER && yIndex == rowCount - 1) { isDroppingPositionValid = false } else { - gridItems.filterVisibleOnly().forEach { item -> + gridItems.filterVisibleOnCurrentPageOnly().forEach { item -> for (xCell in item.left..item.right) { for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) @@ -481,18 +481,18 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel redrawIcons = true } else { val center = gridCenters.minBy { - Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + Math.abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) } val gridCells = getClosestGridCells(center) if (gridCells != null) { - xIndex = gridCells.first - yIndex = gridCells.second + xIndex = gridCells.x + yIndex = gridCells.y // check if the destination cell is empty or a folder isDroppingPositionValid = true val wantedCell = Pair(xIndex, yIndex) - gridItems.filterVisibleOnly().forEach { item -> + gridItems.filterVisibleOnCurrentPageOnly().forEach { item -> for (xCell in item.left..item.right) { for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) @@ -700,7 +700,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun addWidget() { val center = gridCenters.minBy { - Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + Math.abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) } val gridCells = getClosestGridCells(center) @@ -714,7 +714,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } var areAllCellsEmpty = true - gridItems.filterVisibleOnly().filter { it.id != draggedItem?.id }.forEach { item -> + gridItems.filterVisibleOnCurrentPageOnly().filter { it.id != draggedItem?.id }.forEach { item -> for (xCell in item.left..item.right) { for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) @@ -759,8 +759,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val widgetView = widgetViews.firstOrNull { it.tag == widgetItem.widgetId } if (widgetView != null && !widgetItem.outOfBounds()) { post { - widgetView.x = calculateWidgetX(widgetItem.left) - widgetView.y = calculateWidgetY(widgetItem.top) + val widgetPos = calculateWidgetPos(widgetItem.getTopLeft()) + widgetView.x = widgetPos.x.toFloat() + widgetView.y = widgetPos.y.toFloat() widgetView.beVisible() } } @@ -847,9 +848,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun updateWidgetPositionAndSize(widgetView: AppWidgetHostView, item: HomeScreenGridItem): Size { val currentViewPosition = pager.getCurrentViewPositionInFullPageSpace() * width.toFloat() - val x = calculateWidgetX(item.left) + width * item.page - currentViewPosition - widgetView.x = x - widgetView.y = calculateWidgetY(item.top) + val widgetPos = calculateWidgetPos(item.getTopLeft()) + widgetView.x = widgetPos.x + width * item.page - currentViewPosition + widgetView.y = widgetPos.y.toFloat() val widgetWidth = item.getWidthInCells() * cellWidth val widgetHeight = item.getHeightInCells() * cellHeight @@ -869,21 +870,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return Size(widgetWidth, widgetHeight) } - private fun calculateWidgetX(leftCell: Int) = cellXCoords[leftCell] + sideMargins.left.toFloat() + extraXMargin - - private fun calculateWidgetY(topCell: Int) = cellYCoords[topCell] + sideMargins.top.toFloat() + extraYMargin + private fun calculateWidgetPos(topLeft: Point): Point { + val cell = cells[topLeft]!! + return Point( + cell.left + sideMargins.left + extraXMargin, + cell.top + sideMargins.top + extraYMargin + ) + } // convert stuff like 102x192 to grid cells like 0x1 - private fun getClosestGridCells(center: Pair): Pair? { - cellXCoords.forEachIndexed { xIndex, xCell -> - cellYCoords.forEachIndexed { yIndex, yCell -> - if (xCell + cellWidth / 2 == center.first && yCell + cellHeight / 2 == center.second) { - return Pair(xIndex, yIndex) - } - } - } - - return null + private fun getClosestGridCells(center: Point): Point? { + return cells.entries.firstOrNull { (_, cell) -> center.x == cell.centerX() && center.y == cell.centerY() }?.key } private fun redrawGrid() { @@ -900,66 +897,22 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel @SuppressLint("DrawAllocation") override fun onDraw(canvas: Canvas) { super.onDraw(canvas) - if (cellXCoords.isEmpty()) { + if (cells.isEmpty()) { fillCellSizes() } val currentXFactor = pager.getXFactorForCurrentPage() val lastXFactor = pager.getXFactorForLastPage() - fun handleGridItemDrawing(item: HomeScreenGridItem, baseItemX: Int, baseItemY: Int) { - if (item.id != draggedItem?.id) { - val drawableX = baseItemX + iconMargin + extraXMargin - - val drawable = if (item.type == ITEM_TYPE_FOLDER) { - item.toFolder().generateDrawable() - } else { - item.drawable!! - } - - if (item.docked) { - val drawableY = cellYCoords[rowCount - 1] + cellHeight - iconMargin - iconSize + sideMargins.top - - drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) - } else { - val drawableY = baseItemY + iconMargin + extraYMargin - drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) - - if (item.id != draggedItem?.id && item.title.isNotEmpty()) { - val textX = baseItemX.toFloat() + labelSideMargin - val textY = baseItemY.toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin - val textPaintToUse = if (item.parentId == null) { - textPaint - } else { - contrastTextPaint - } - val staticLayout = StaticLayout.Builder - .obtain(item.title, 0, item.title.length, textPaintToUse, cellWidth - 2 * labelSideMargin) - .setMaxLines(2) - .setEllipsize(TextUtils.TruncateAt.END) - .setAlignment(Layout.Alignment.ALIGN_CENTER) - .build() - - canvas.save() - canvas.translate(textX, textY) - staticLayout.draw(canvas) - canvas.restore() - } - } - - drawable?.draw(canvas) + fun handleMainGridItemDrawing(item: HomeScreenGridItem, xFactor: Float) { + val offsetX = sideMargins.left + (this@HomeScreenGrid.width * xFactor).toInt() + val offsetY = sideMargins.top + cells[item.getTopLeft()]!!.withOffset(offsetX, offsetY) { + canvas.drawItemInCell(item, this) } } - fun handleMainGridItemDrawing(item: HomeScreenGridItem, xFactor: Float) { - handleGridItemDrawing( - item, - cellXCoords[item.left] + sideMargins.left + (width * xFactor).toInt(), - cellYCoords[item.top] + sideMargins.top - ) - } - - 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 } + gridItems.filter { it.isSingleCellType() && pager.isItemOnCurrentPage(it) && !it.docked && it.parentId == null } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -967,7 +920,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel handleMainGridItemDrawing(item, currentXFactor) } - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT || it.type == ITEM_TYPE_FOLDER) && it.docked && it.parentId == null } + gridItems.filter { it.isSingleCellType() && it.docked && it.parentId == null } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -976,7 +929,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel handleMainGridItemDrawing(item, 0f) } 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 } + gridItems.filter { it.isSingleCellType() && pager.isItemOnLastPage(it) && !it.docked && it.parentId == null } .forEach { item -> if (item.outOfBounds()) { return@forEach @@ -988,7 +941,7 @@ 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) + it.isSingleCellType() && pager.isItemInSwipeRange(it) && !it.docked && it.parentId == null @@ -1020,7 +973,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val usableWidth = getFakeWidth() val pageIndicatorsStart = (usableWidth - pageIndicatorsRequiredWidth) / 2 + sideMargins.left var currentPageIndicatorLeft = pageIndicatorsStart - val pageIndicatorY = cellYCoords[rowCount - 1].toFloat() + sideMargins.top + extraYMargin + iconMargin + val pageIndicatorY = dockTop.toFloat() + sideMargins.top + extraYMargin + iconMargin val pageIndicatorStep = pageIndicatorRadius * 2 + pageIndicatorMargin emptyPageIndicatorPaint.alpha = pager.getPageChangeIndicatorsAlpha() // Draw empty page indicators @@ -1063,24 +1016,27 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel .setAlignment(Layout.Alignment.ALIGN_CENTER) .build() - canvas.save() - canvas.translate(textX, textY) - staticLayout.draw(canvas) - canvas.restore() + withTranslation(textX, textY) { + staticLayout.draw(canvas) + } items.forEach { item -> val (row, column) = folder.getItemPosition(item) - handleGridItemDrawing( - item, - (folderItemsRect.left + column * cellWidth).roundToInt(), - (folderItemsRect.top + row * cellHeight).roundToInt() + val left = (folderItemsRect.left + column * cellWidth).roundToInt() + val top = (folderItemsRect.top + row * cellHeight).roundToInt() + val rect = Rect( + left, + top, + left + cellWidth, + top + cellHeight ) + canvas.drawItemInCell(item, rect) } } } if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { - if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) { + if (draggedItem!!.isSingleCellType()) { if (folder != null && folder.getItemsDrawingRect().contains( (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), (sideMargins.top + draggedItemCurrentCoords.second).toFloat() @@ -1098,16 +1054,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } else { // draw a circle under the current cell val center = gridCenters.minBy { - abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) } val gridCells = getClosestGridCells(center) if (gridCells != null) { - val shadowX = cellXCoords[gridCells.first] + iconMargin + iconSize / 2f + extraXMargin + sideMargins.left - val shadowY = if (gridCells.second == rowCount - 1) { - cellYCoords[gridCells.second] + cellHeight - iconMargin - iconSize / 2f + val cell = cells[gridCells]!! + val shadowX = cell.left + iconMargin + iconSize / 2f + extraXMargin + sideMargins.left + val shadowY = if (gridCells.y == rowCount - 1) { + cell.top + cellHeight - iconMargin - iconSize / 2f } else { - cellYCoords[gridCells.second] + iconMargin + iconSize / 2f + extraYMargin + cell.top + iconMargin + iconSize / 2f + extraYMargin } + sideMargins.top canvas.drawCircle(shadowX, shadowY, iconSize / 2f, dragShadowCirclePaint) @@ -1123,14 +1080,15 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel // at first draw we are loading the widget from the database at some exact spot, not dragging it if (!isFirstDraw) { val center = gridCenters.minBy { - Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + Math.abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) } val gridCells = getClosestGridCells(center) if (gridCells != null) { val widgetRect = getWidgetOccupiedRect(gridCells) - val leftSide = calculateWidgetX(widgetRect.left) - val topSide = calculateWidgetY(widgetRect.top) + val widgetPos = calculateWidgetPos(Point(widgetRect.left, widgetRect.top)) + val leftSide = widgetPos.x.toFloat() + val topSide = widgetPos.y.toFloat() val rightSide = leftSide + draggedItem!!.getWidthInCells() * cellWidth val bottomSide = topSide + draggedItem!!.getHeightInCells() * cellHeight canvas.drawRoundRect(leftSide, topSide, rightSide, bottomSide, roundedCornerRadius, roundedCornerRadius, dragShadowCirclePaint) @@ -1170,17 +1128,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel 0 } iconSize = min(cellWidth, cellHeight) - 2 * iconMargin + dockTop = (context.config.homeRowCount - 1) * cellHeight for (i in 0 until context.config.homeColumnCount) { - cellXCoords.add(i, i * cellWidth) - } - - for (i in 0 until context.config.homeRowCount) { - cellYCoords.add(i, i * cellHeight) - } - - cellXCoords.forEach { x -> - cellYCoords.forEach { y -> - gridCenters.add(Pair(x + cellWidth / 2, y + cellHeight / 2)) + for (j in 0 until context.config.homeRowCount) { + val rect = Rect( + i * cellWidth, + j * cellHeight, + (i + 1) * cellWidth, + (j + 1) * cellHeight, + ) + cells[Point(i, j)] = rect + gridCenters.add(Point(rect.centerX(), rect.centerY())) } } } @@ -1200,7 +1158,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel // get the clickable area around the icon, it includes text too fun getClickableRect(item: HomeScreenGridItem): Rect { - if (cellXCoords.isEmpty()) { + if (cells.isEmpty()) { fillCellSizes() } @@ -1213,11 +1171,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt() clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() } else { - clickableLeft = cellXCoords[item.left] + sideMargins.left + extraXMargin + val cell = cells[item.getTopLeft()]!! + clickableLeft = cell.left + sideMargins.left + extraXMargin clickableTop = if (item.docked) { - cellYCoords[item.getDockAdjustedTop(rowCount)] + cellHeight - iconSize - iconMargin + dockTop + cellHeight - iconSize - iconMargin } else { - cellYCoords[item.top] - iconMargin + extraYMargin + cell.top - iconMargin + extraYMargin } + sideMargins.top } @@ -1225,9 +1184,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } // drag the center of the widget, not the top left corner - private fun getWidgetOccupiedRect(item: Pair): Rect { - val left = item.first - floor((draggedItem!!.getWidthInCells() - 1) / 2.0).toInt() - val rect = Rect(left, item.second, left + draggedItem!!.getWidthInCells() - 1, item.second + draggedItem!!.getHeightInCells() - 1) + private fun getWidgetOccupiedRect(item: Point): Rect { + val left = item.x - floor((draggedItem!!.getWidthInCells() - 1) / 2.0).toInt() + val rect = Rect(left, item.y, left + draggedItem!!.getWidthInCells() - 1, item.y + draggedItem!!.getHeightInCells() - 1) if (rect.left < 0) { rect.right -= rect.left rect.left = 0 @@ -1259,19 +1218,20 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - for (gridItem in gridItems.filterVisibleOnly()) { + for (gridItem in gridItems.filterVisibleOnCurrentPageOnly()) { if (gridItem.outOfBounds()) { continue } - if (gridItem.type == ITEM_TYPE_ICON || gridItem.type == ITEM_TYPE_SHORTCUT || gridItem.type == ITEM_TYPE_FOLDER) { + if (gridItem.isSingleCellType()) { val rect = getClickableRect(gridItem) if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { return gridItem } } else if (gridItem.type == ITEM_TYPE_WIDGET) { - val left = calculateWidgetX(gridItem.left) - val top = calculateWidgetY(gridItem.top) + val widgetPos = calculateWidgetPos(gridItem.getTopLeft()) + val left = widgetPos.x.toFloat() + val top = widgetPos.y.toFloat() val right = left + gridItem.getWidthInCells() * cellWidth val bottom = top + gridItem.getHeightInCells() * cellHeight @@ -1293,7 +1253,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun HomeScreenGridItem.outOfBounds(): Boolean { - return (left >= cellXCoords.size || right >= cellXCoords.size || (!docked && (top >= cellYCoords.size - 1 || bottom >= cellYCoords.size - 1))) + return (left >= columnCount || right >= columnCount || (!docked && (top >= rowCount - 1 || bottom >= rowCount - 1))) } private inner class HomeScreenGridTouchHelper(host: View) : ExploreByTouchHelper(host) { @@ -1398,7 +1358,63 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } - private fun ArrayList.filterVisibleOnly() = filter { (pager.isItemOnCurrentPage(it) || it.docked) && it.parentId == null } + private fun Canvas.drawItemInCell(item: HomeScreenGridItem, cell: Rect) { + if (item.id != draggedItem?.id) { + val drawableX = cell.left + iconMargin + extraXMargin + + val drawable = if (item.type == ITEM_TYPE_FOLDER) { + item.toFolder().generateDrawable() + } else { + item.drawable!! + } + + if (item.docked) { + val drawableY = dockTop + cellHeight - iconMargin - iconSize + sideMargins.top + + drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) + } else { + val drawableY = cell.top + iconMargin + extraYMargin + drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) + + if (item.id != draggedItem?.id && item.title.isNotEmpty()) { + val textX = cell.left.toFloat() + labelSideMargin + val textY = cell.top.toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin + val textPaintToUse = if (item.parentId == null) { + textPaint + } else { + contrastTextPaint + } + val staticLayout = StaticLayout.Builder + .obtain(item.title, 0, item.title.length, textPaintToUse, cellWidth - 2 * labelSideMargin) + .setMaxLines(2) + .setEllipsize(TextUtils.TruncateAt.END) + .setAlignment(Layout.Alignment.ALIGN_CENTER) + .build() + + withTranslation(textX, textY) { + staticLayout.draw(this) + } + } + } + + drawable?.draw(this) + } + } + + private fun Rect.withOffset(offsetX: Int, offsetY: Int, block: Rect.() -> Unit) { + offset(offsetX, offsetY) + try { + block() + } finally { + offset(-offsetX, -offsetY) + } + } + + private fun ArrayList.filterVisibleOnCurrentPageOnly() = filter { it.visibleOnCurrentPage() } + + private fun HomeScreenGridItem.visibleOnCurrentPage() = (pager.isItemOnCurrentPage(this) || docked) && parentId == null + + private fun HomeScreenGridItem.isSingleCellType() = (drawable != null && type == ITEM_TYPE_ICON || type == ITEM_TYPE_SHORTCUT || type == ITEM_TYPE_FOLDER) private fun HomeScreenGridItem.toFolder(animateOpening: Boolean = false) = HomeScreenFolder(this, animateOpening) @@ -1435,7 +1451,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } fun getItems() = - gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == item.id } + gridItems.filter { it.isSingleCellType() && it.parentId == item.id } fun generateDrawable(): Drawable? { if (iconSize == 0) { @@ -1474,8 +1490,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } val columnsCount = ceil(sqrt(count.toDouble())).toInt() val rowsCount = ceil(count.toFloat() / columnsCount).toInt() - val centerX = cellXCoords[item.left] + cellWidth / 2 + sideMargins.left - val centerY = cellYCoords[item.top] + cellHeight / 2 + sideMargins.top + val cell = cells[item.getTopLeft()]!! + val centerX = sideMargins.left + cell.centerX() + val centerY = sideMargins.top + cell.centerY() val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) * finalScale val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize) * finalScale var folderDialogTop = centerY - folderDialogHeight / 2 @@ -1560,17 +1577,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } companion object { - private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L - private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L private const val FOLDER_OPEN_HOLD_THRESHOLD = 500L private const val FOLDER_CLOSE_HOLD_THRESHOLD = 300L private const val FOLDER_ANIMATION_DURATION = 200L - - private enum class PageChangeArea { - LEFT, - MIDDLE, - RIGHT - } } } From 3dd78cc911b4922fa7d5cc6cf9e85b8fbc531bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Thu, 24 Aug 2023 13:18:46 +0200 Subject: [PATCH 19/24] Fix folder opening and closing issues --- .../launcher/views/HomeScreenGrid.kt | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) 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 51fddfc..0a61d1b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -270,7 +270,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } currentlyOpenFolder?.also { folder -> - if (folder.getDrawingRect().contains(x.toFloat(), y.toFloat())) { + if (folder.getDrawingRect(overrideScale = 1f).contains(x.toFloat(), y.toFloat())) { draggingLeftFolderAt = null } else { draggingLeftFolderAt.also { @@ -296,32 +296,34 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel draggedItemCurrentCoords = Pair(x, y) - val center = gridCenters.minBy { - abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) - } - val coveredCell = getClosestGridCells(center) - if (coveredCell != null) { - val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.x && it.top == coveredCell.y } - if (coveredFolder != null && coveredFolder.id != draggedItem?.id) { - draggingEnteredNewFolderAt.also { - if (it == null) { - draggingEnteredNewFolderAt = System.currentTimeMillis() - } else if (System.currentTimeMillis() - it > FOLDER_OPEN_HOLD_THRESHOLD) { - if (coveredFolder.toFolder().getItems() - .count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id - ) { - performHapticFeedback() - draggingEnteredNewFolderAt = null - } else { - openFolder(coveredFolder) + if (draggedItem?.type != ITEM_TYPE_FOLDER && draggedItem?.type != ITEM_TYPE_WIDGET) { + val center = gridCenters.minBy { + abs(it.x - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.y - draggedItemCurrentCoords.second + sideMargins.top) + } + val coveredCell = getClosestGridCells(center) + if (coveredCell != null) { + val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.x && it.top == coveredCell.y } + if (coveredFolder != null && coveredFolder.id != draggedItem?.id) { + draggingEnteredNewFolderAt.also { + if (it == null) { + draggingEnteredNewFolderAt = System.currentTimeMillis() + } else if (System.currentTimeMillis() - it > FOLDER_OPEN_HOLD_THRESHOLD) { + if (coveredFolder.toFolder().getItems() + .count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id + ) { + performHapticFeedback() + draggingEnteredNewFolderAt = null + } else { + openFolder(coveredFolder) + } } } + } else { + draggingEnteredNewFolderAt = null } } else { draggingEnteredNewFolderAt = null } - } else { - draggingEnteredNewFolderAt = null } pager.handleItemMovement(x, y) @@ -492,7 +494,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel // check if the destination cell is empty or a folder isDroppingPositionValid = true val wantedCell = Pair(xIndex, yIndex) - gridItems.filterVisibleOnCurrentPageOnly().forEach { item -> + gridItems.filterVisibleOnCurrentPageOnly().filter { it.id != draggedItem?.id }.forEach { item -> for (xCell in item.left..item.right) { for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) @@ -626,7 +628,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel context.homeScreenGridItemsDB.shiftFolderItems(oldParentId, oldLeft, -1, id) } - if (newParentId != null && gridItems.any { it.parentId == newParentId && it.left == left }) { + if (newParentId != null && gridItems.any { it.parentId == newParentId && it.left == left } && (newParentId != oldParentId || left != oldLeft)) { gridItems.filter { it.parentId == newParentId && it.left >= left && it.id != id }.forEach { it.left += 1 } @@ -989,7 +991,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } val folder = currentlyOpenFolder - if (folder != null) { + if (folder != null && folder.getItems().isNotEmpty()) { val items = folder.getItems() val folderRect = folder.getDrawingRect() val folderItemsRect = folder.getItemsDrawingRect() @@ -1344,7 +1346,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (currentlyOpenFolder == null) { currentlyOpenFolder = folder.toFolder(animateOpening = true) redrawGrid() - } else { + } else if (currentlyOpenFolder?.item?.id != folder.id ){ closeFolder() } } From 6d3b138459db89c1864e023d18c583771fe4cfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Thu, 24 Aug 2023 13:21:28 +0200 Subject: [PATCH 20/24] Fix shadow drawing in folders --- .../simplemobiletools/launcher/views/HomeScreenGrid.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 0a61d1b..63cdcf7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -469,8 +469,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val folder = currentlyOpenFolder if (folder != null && folder.getItemsDrawingRect().contains( - (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), - (sideMargins.top + draggedItemCurrentCoords.second).toFloat() + (draggedItemCurrentCoords.first).toFloat(), + (draggedItemCurrentCoords.second).toFloat() ) ) { val center = folder.getItemsGridCenters().minBy { @@ -1040,8 +1040,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { if (draggedItem!!.isSingleCellType()) { if (folder != null && folder.getItemsDrawingRect().contains( - (sideMargins.left + draggedItemCurrentCoords.first).toFloat(), - (sideMargins.top + draggedItemCurrentCoords.second).toFloat() + (draggedItemCurrentCoords.first).toFloat(), + (draggedItemCurrentCoords.second).toFloat() ) ) { @@ -1346,7 +1346,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (currentlyOpenFolder == null) { currentlyOpenFolder = folder.toFolder(animateOpening = true) redrawGrid() - } else if (currentlyOpenFolder?.item?.id != folder.id ){ + } else if (currentlyOpenFolder?.item?.id != folder.id) { closeFolder() } } From e5adb450c2258f4d6e0d5f257dc19f670d472ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Thu, 24 Aug 2023 13:29:23 +0200 Subject: [PATCH 21/24] Fix animation scaling when opening and closing folders --- .../launcher/views/HomeScreenGrid.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 63cdcf7..0bc07e5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -270,7 +270,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } currentlyOpenFolder?.also { folder -> - if (folder.getDrawingRect(overrideScale = 1f).contains(x.toFloat(), y.toFloat())) { + if (folder.getDrawingRect().contains(x.toFloat(), y.toFloat())) { draggingLeftFolderAt = null } else { draggingLeftFolderAt.also { @@ -1000,9 +1000,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel 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()) { + canvas.drawRoundRect(folderRect, roundedCornerRadius / folder.scale, roundedCornerRadius / folder.scale, folderBackgroundPaint) val textX = folderRect.left + folderPadding val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize val staticLayout = StaticLayout.Builder @@ -1484,8 +1483,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return BitmapDrawable(resources, bitmap) } - fun getDrawingRect(overrideScale: Float? = null): RectF { - val finalScale = overrideScale ?: scale + fun getDrawingRect(): RectF { val count = getItems().count() if (count == 0) { return RectF(0f, 0f, 0f, 0f) @@ -1495,8 +1493,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val cell = cells[item.getTopLeft()]!! val centerX = sideMargins.left + cell.centerX() val centerY = sideMargins.top + cell.centerY() - val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) * finalScale - val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize) * finalScale + val folderDialogWidth = columnsCount * cellWidth + 2 * folderPadding + val folderDialogHeight = rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize var folderDialogTop = centerY - folderDialogHeight / 2 var folderDialogLeft = centerX - folderDialogWidth / 2 @@ -1517,7 +1515,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } fun getItemsDrawingRect(): RectF { - val folderRect = getDrawingRect(overrideScale = 1f) + val folderRect = getDrawingRect() return RectF( folderRect.left + folderPadding, folderRect.top + folderPadding * 2 + folderTitleTextPaint.textSize, From 1fcda210b6675133927b1523baf5af310840bc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 25 Aug 2023 09:46:51 +0200 Subject: [PATCH 22/24] Add vibration on folder items too --- .../com/simplemobiletools/launcher/activities/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 751843f..88b971a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -583,7 +583,7 @@ class MainActivity : SimpleActivity(), FlingListener { } private fun performItemLongClick(x: Float, clickedGridItem: HomeScreenGridItem) { - if (clickedGridItem.type == ITEM_TYPE_ICON || clickedGridItem.type == ITEM_TYPE_SHORTCUT) { + if (clickedGridItem.type == ITEM_TYPE_ICON || clickedGridItem.type == ITEM_TYPE_SHORTCUT || clickedGridItem.type == ITEM_TYPE_FOLDER) { binding.mainHolder.performHapticFeedback() } From e88eb05e0038ac34e37c6279a4969789e05fe8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 25 Aug 2023 11:14:13 +0200 Subject: [PATCH 23/24] Remove needless extra space for folders --- .../launcher/views/HomeScreenGrid.kt | 89 ++++++++++--------- 1 file changed, 48 insertions(+), 41 deletions(-) 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 0bc07e5..0f08230 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -49,12 +49,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private lateinit var binding: HomeScreenGridBinding private var columnCount = context.config.homeColumnCount private var rowCount = context.config.homeRowCount - private var dockTop = 0 + private var pageIndicatorsYPos = 0 private val cells = mutableMapOf() + private var dockCellY = 0 var cellWidth = 0 var cellHeight = 0 - private var extraXMargin = 0 - private var extraYMargin = 0 private var iconMargin = (context.resources.getDimension(R.dimen.icon_side_margin) * 5 / columnCount).toInt() private var labelSideMargin = context.resources.getDimension(com.simplemobiletools.commons.R.dimen.small_margin).toInt() @@ -875,8 +874,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun calculateWidgetPos(topLeft: Point): Point { val cell = cells[topLeft]!! return Point( - cell.left + sideMargins.left + extraXMargin, - cell.top + sideMargins.top + extraYMargin + cell.left + sideMargins.left, + cell.top + sideMargins.top ) } @@ -975,7 +974,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val usableWidth = getFakeWidth() val pageIndicatorsStart = (usableWidth - pageIndicatorsRequiredWidth) / 2 + sideMargins.left var currentPageIndicatorLeft = pageIndicatorsStart - val pageIndicatorY = dockTop.toFloat() + sideMargins.top + extraYMargin + iconMargin + val pageIndicatorY = pageIndicatorsYPos.toFloat() + sideMargins.top + iconMargin val pageIndicatorStep = pageIndicatorRadius * 2 + pageIndicatorMargin emptyPageIndicatorPaint.alpha = pager.getPageChangeIndicatorsAlpha() // Draw empty page indicators @@ -995,6 +994,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val items = folder.getItems() val folderRect = folder.getDrawingRect() val folderItemsRect = folder.getItemsDrawingRect() + val cellSize = min(cellWidth, cellHeight) val currentViewPosition = pager.getCurrentViewPositionInFullPageSpace() * width.toFloat() val rectOffset = width * folder.item.page - currentViewPosition @@ -1023,13 +1023,13 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel items.forEach { item -> val (row, column) = folder.getItemPosition(item) - val left = (folderItemsRect.left + column * cellWidth).roundToInt() - val top = (folderItemsRect.top + row * cellHeight).roundToInt() + val left = (folderItemsRect.left + column * cellSize).roundToInt() + val top = (folderItemsRect.top + row * cellSize).roundToInt() val rect = Rect( left, top, - left + cellWidth, - top + cellHeight + left + cellSize, + top + cellSize ) canvas.drawItemInCell(item, rect) } @@ -1047,11 +1047,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val center = folder.getItemsGridCenters().minBy { abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) } + val cellSize = min(cellWidth, cellHeight) - val shadowX = center.second - cellWidth / 2 + iconMargin + iconSize / 2f + extraXMargin - val shadowY = center.third - cellHeight / 2 + iconMargin + iconSize / 2f + extraYMargin + val shadowX = center.second - cellSize / 2 + iconMargin + iconSize / 2f + val shadowY = center.third - cellSize / 2 + iconMargin + iconSize / 2 - canvas.drawCircle(shadowX, shadowY, iconSize / 2f, dragShadowCirclePaint) + canvas.drawCircle(shadowX, shadowY.toFloat(), iconSize / 2f, dragShadowCirclePaint) } else { // draw a circle under the current cell val center = gridCenters.minBy { @@ -1061,12 +1062,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val gridCells = getClosestGridCells(center) if (gridCells != null) { val cell = cells[gridCells]!! - val shadowX = cell.left + iconMargin + iconSize / 2f + extraXMargin + sideMargins.left + val shadowX = cell.left + iconMargin + iconSize / 2f + sideMargins.left val shadowY = if (gridCells.y == rowCount - 1) { - cell.top + cellHeight - iconMargin - iconSize / 2f + cellHeight - iconMargin - iconSize / 2f } else { - cell.top + iconMargin + iconSize / 2f + extraYMargin - } + sideMargins.top + iconMargin + iconSize / 2f + } + sideMargins.top + cell.top canvas.drawCircle(shadowX, shadowY, iconSize / 2f, dragShadowCirclePaint) } @@ -1116,30 +1117,34 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun fillCellSizes() { - cellWidth = getFakeWidth() / context.config.homeColumnCount - cellHeight = getFakeHeight() / context.config.homeRowCount - extraXMargin = if (cellWidth > cellHeight) { + cellWidth = getFakeWidth() / columnCount + cellHeight = getFakeHeight() / rowCount + val extraXMargin = if (cellWidth > cellHeight) { (cellWidth - cellHeight) / 2 } else { 0 } - extraYMargin = if (cellHeight > cellWidth) { + val extraYMargin = if (cellHeight > cellWidth) { (cellHeight - cellWidth) / 2 } else { 0 } iconSize = min(cellWidth, cellHeight) - 2 * iconMargin - dockTop = (context.config.homeRowCount - 1) * cellHeight - for (i in 0 until context.config.homeColumnCount) { - for (j in 0 until context.config.homeRowCount) { + pageIndicatorsYPos = (rowCount - 1) * cellHeight + extraYMargin + for (i in 0 until columnCount) { + for (j in 0 until rowCount) { + val yMarginToAdd = if (j == rowCount - 1) 0 else extraYMargin val rect = Rect( - i * cellWidth, - j * cellHeight, - (i + 1) * cellWidth, - (j + 1) * cellHeight, + i * cellWidth + extraXMargin, + j * cellHeight + yMarginToAdd, + (i + 1) * cellWidth - extraXMargin, + (j + 1) * cellHeight - yMarginToAdd, ) cells[Point(i, j)] = rect gridCenters.add(Point(rect.centerX(), rect.centerY())) + if (j == rowCount - 1) { + dockCellY = j * cellHeight + } } } } @@ -1169,15 +1174,15 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (folder != null && item.parentId == folder.item.id) { val folderRect = folder.getItemsDrawingRect() val (row, column) = folder.getItemPosition(item) - clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt() - clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() + clickableLeft = (folderRect.left + column * cellWidth).toInt() + clickableTop = (folderRect.top + row * cellHeight - iconMargin).toInt() } else { val cell = cells[item.getTopLeft()]!! - clickableLeft = cell.left + sideMargins.left + extraXMargin + clickableLeft = cell.left + sideMargins.left clickableTop = if (item.docked) { - dockTop + cellHeight - iconSize - iconMargin + dockCellY + cellHeight - iconSize - iconMargin } else { - cell.top - iconMargin + extraYMargin + cell.top - iconMargin } + sideMargins.top } @@ -1361,7 +1366,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel private fun Canvas.drawItemInCell(item: HomeScreenGridItem, cell: Rect) { if (item.id != draggedItem?.id) { - val drawableX = cell.left + iconMargin + extraXMargin + val drawableX = cell.left + iconMargin val drawable = if (item.type == ITEM_TYPE_FOLDER) { item.toFolder().generateDrawable() @@ -1370,16 +1375,16 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } if (item.docked) { - val drawableY = dockTop + cellHeight - iconMargin - iconSize + sideMargins.top + val drawableY = dockCellY + cellHeight - iconMargin - iconSize + sideMargins.top drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) } else { - val drawableY = cell.top + iconMargin + extraYMargin + val drawableY = cell.top + iconMargin drawable?.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) if (item.id != draggedItem?.id && item.title.isNotEmpty()) { val textX = cell.left.toFloat() + labelSideMargin - val textY = cell.top.toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin + val textY = cell.top.toFloat() + iconSize + iconMargin + labelSideMargin val textPaintToUse = if (item.parentId == null) { textPaint } else { @@ -1490,11 +1495,12 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } val columnsCount = ceil(sqrt(count.toDouble())).toInt() val rowsCount = ceil(count.toFloat() / columnsCount).toInt() + val cellSize = min(cellWidth, cellHeight) val cell = cells[item.getTopLeft()]!! val centerX = sideMargins.left + cell.centerX() val centerY = sideMargins.top + cell.centerY() - val folderDialogWidth = columnsCount * cellWidth + 2 * folderPadding - val folderDialogHeight = rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize + val folderDialogWidth = columnsCount * cellSize + 2 * folderPadding + val folderDialogHeight = rowsCount * cellSize + 3 * folderPadding + folderTitleTextPaint.textSize var folderDialogTop = centerY - folderDialogHeight / 2 var folderDialogLeft = centerX - folderDialogWidth / 2 @@ -1529,14 +1535,15 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() val folderItemsRect = getItemsDrawingRect() + val cellSize = min(cellWidth, cellHeight) return (0 until columnsCount * rowsCount) .toList() .map { Pair(it % columnsCount, it / columnsCount) } .mapIndexed { index, (x, y) -> Triple( index, - (folderItemsRect.left + x * cellWidth + cellWidth / 2).toInt(), - (folderItemsRect.top + y * cellHeight + cellHeight / 2).toInt() + (folderItemsRect.left + x * cellSize + cellSize / 2).toInt(), + (folderItemsRect.top + y * cellSize + cellSize / 2).toInt() ) } } From b7eecc5663e3818c805257aa87aa084ad5f8bd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Fri, 25 Aug 2023 15:40:36 +0200 Subject: [PATCH 24/24] Add enough gaps between folder items to show item names --- .../launcher/views/HomeScreenGrid.kt | 73 ++++++++++++------- 1 file changed, 47 insertions(+), 26 deletions(-) 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 0f08230..7892f0e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -302,7 +302,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val coveredCell = getClosestGridCells(center) if (coveredCell != null) { val coveredFolder = gridItems.firstOrNull { it.type == ITEM_TYPE_FOLDER && it.left == coveredCell.x && it.top == coveredCell.y } - if (coveredFolder != null && coveredFolder.id != draggedItem?.id) { + if (coveredFolder != null && coveredFolder.id != draggedItem?.id && currentlyOpenFolder == null) { draggingEnteredNewFolderAt.also { if (it == null) { draggingEnteredNewFolderAt = System.currentTimeMillis() @@ -993,8 +993,6 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel if (folder != null && folder.getItems().isNotEmpty()) { val items = folder.getItems() val folderRect = folder.getDrawingRect() - val folderItemsRect = folder.getItemsDrawingRect() - val cellSize = min(cellWidth, cellHeight) val currentViewPosition = pager.getCurrentViewPositionInFullPageSpace() * width.toFloat() val rectOffset = width * folder.item.page - currentViewPosition @@ -1003,7 +1001,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel canvas.withScale(folder.scale, folder.scale, folderRect.centerX(), folderRect.centerY()) { canvas.drawRoundRect(folderRect, roundedCornerRadius / folder.scale, roundedCornerRadius / folder.scale, folderBackgroundPaint) val textX = folderRect.left + folderPadding - val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize + val textY = folderRect.top + folderPadding val staticLayout = StaticLayout.Builder .obtain( folder.item.title, @@ -1022,16 +1020,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } items.forEach { item -> - val (row, column) = folder.getItemPosition(item) - val left = (folderItemsRect.left + column * cellSize).roundToInt() - val top = (folderItemsRect.top + row * cellSize).roundToInt() - val rect = Rect( - left, - top, - left + cellSize, - top + cellSize - ) - canvas.drawItemInCell(item, rect) + val itemRect = folder.getItemRect(item) +// canvas.drawRect(itemRect, contrastTextPaint) + canvas.drawItemInCell(item, itemRect) } } } @@ -1047,7 +1038,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val center = folder.getItemsGridCenters().minBy { abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) } - val cellSize = min(cellWidth, cellHeight) + val cellSize = folder.getCellSize() val shadowX = center.second - cellSize / 2 + iconMargin + iconSize / 2f val shadowY = center.third - cellSize / 2 + iconMargin + iconSize / 2 @@ -1172,10 +1163,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val clickableLeft: Int val clickableTop: Int if (folder != null && item.parentId == folder.item.id) { - val folderRect = folder.getItemsDrawingRect() - val (row, column) = folder.getItemPosition(item) - clickableLeft = (folderRect.left + column * cellWidth).toInt() - clickableTop = (folderRect.top + row * cellHeight - iconMargin).toInt() + val itemRect = folder.getItemRect(item) + clickableLeft = itemRect.left + clickableTop = itemRect.top - iconMargin } else { val cell = cells[item.getTopLeft()]!! clickableLeft = cell.left + sideMargins.left @@ -1495,12 +1485,14 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } val columnsCount = ceil(sqrt(count.toDouble())).toInt() val rowsCount = ceil(count.toFloat() / columnsCount).toInt() - val cellSize = min(cellWidth, cellHeight) + val cellSize = getCellSize() + val gap = getGapSize() + val yGap = gap + textPaint.textSize + 2 * labelSideMargin val cell = cells[item.getTopLeft()]!! val centerX = sideMargins.left + cell.centerX() val centerY = sideMargins.top + cell.centerY() - val folderDialogWidth = columnsCount * cellSize + 2 * folderPadding - val folderDialogHeight = rowsCount * cellSize + 3 * folderPadding + folderTitleTextPaint.textSize + val folderDialogWidth = columnsCount * cellSize + 2 * folderPadding + (columnsCount - 1) * gap + val folderDialogHeight = rowsCount * cellSize + 3 * folderPadding + folderTitleTextPaint.textSize + rowsCount * yGap var folderDialogTop = centerY - folderDialogHeight / 2 var folderDialogLeft = centerX - folderDialogWidth / 2 @@ -1535,20 +1527,22 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt() val folderItemsRect = getItemsDrawingRect() - val cellSize = min(cellWidth, cellHeight) + val cellSize = getCellSize() + val gap = getGapSize() + val yGap = gap + textPaint.textSize + 2 * labelSideMargin return (0 until columnsCount * rowsCount) .toList() .map { Pair(it % columnsCount, it / columnsCount) } .mapIndexed { index, (x, y) -> Triple( index, - (folderItemsRect.left + x * cellSize + cellSize / 2).toInt(), - (folderItemsRect.top + y * cellSize + cellSize / 2).toInt() + (folderItemsRect.left + x * cellSize + x * gap + cellSize / 2).toInt(), + (folderItemsRect.top + y * cellSize + y * yGap + cellSize / 2).toInt() ) } } - fun getItemPosition(item: HomeScreenGridItem): Pair { + private fun getItemPosition(item: HomeScreenGridItem): Pair { val count = getItems().count() val columnsCount = ceil(sqrt(count.toDouble())).roundToInt() val column = item.left % columnsCount @@ -1556,6 +1550,22 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return Pair(row, column) } + fun getItemRect(item: HomeScreenGridItem): Rect { + val (row, column) = getItemPosition(item) + val itemsRect = getItemsDrawingRect() + val cellSize = getCellSize() + val gapSize = getGapSize() + val yGapSize = gapSize + textPaint.textSize + 2 * labelSideMargin + val left = (itemsRect.left + column * cellSize + column * gapSize).roundToInt() + val top = (itemsRect.top + row * cellSize + row * yGapSize).roundToInt() + return Rect( + left, + top, + left + cellSize, + top + cellSize + ) + } + fun animateClosing(callback: () -> Unit) { post { if (closing) { @@ -1581,6 +1591,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } } + + fun getCellSize(): Int = min(cellWidth, cellHeight) + + private fun getGapSize(): Float { + val cellSize = getCellSize() + return if (cellSize == cellWidth) { + 0f + } else { + cellSize / 5f + } + } } companion object {