diff --git a/app/build.gradle b/app/build.gradle index 1ceaf63..bd4621a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:a9c3b63377' + implementation 'com.github.SimpleMobileTools:Simple-Commons:a26066d67d' 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 0456c9f..f59a589 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/MainActivity.kt @@ -47,6 +47,7 @@ import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.view.* import kotlinx.android.synthetic.main.all_apps_fragment.view.* import kotlinx.android.synthetic.main.widgets_fragment.view.* +import kotlin.math.abs class MainActivity : SimpleActivity(), FlingListener { private val ANIMATION_DURATION = 150L @@ -89,7 +90,7 @@ class MainActivity : SimpleActivity(), FlingListener { mScreenHeight = realScreenSize.y mAllAppsFragmentY = mScreenHeight mWidgetsFragmentY = mScreenHeight - mMoveGestureThreshold = resources.getDimension(R.dimen.move_gesture_threshold).toInt() + mMoveGestureThreshold = resources.getDimensionPixelSize(R.dimen.move_gesture_threshold) arrayOf(all_apps_fragment as MyFragment, widgets_fragment as MyFragment).forEach { fragment -> fragment.setupFragment(this) @@ -97,6 +98,25 @@ class MainActivity : SimpleActivity(), FlingListener { fragment.beVisible() } + handleIntentAction(intent) + + home_screen_grid.itemClickListener = { + performItemClick(it) + } + + home_screen_grid.itemLongClickListener = { + performItemLongClick(home_screen_grid.getClickableRect(it).left.toFloat(), it) + } + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + if (intent != null) { + handleIntentAction(intent) + } + } + + 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) @@ -108,13 +128,14 @@ class MainActivity : SimpleActivity(), FlingListener { 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 rect = findFirstEmptyCell() ?: return@ensureBackgroundThread + val (page, rect) = findFirstEmptyCell() val gridItem = HomeScreenGridItem( null, rect.left, rect.top, rect.right, rect.bottom, + page, item.shortcutInfo!!.`package`, "", label, @@ -124,9 +145,13 @@ class MainActivity : SimpleActivity(), FlingListener { "", shortcutId, icon.toBitmap(), + false, icon ) + runOnUiThread { + home_screen_grid.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) @@ -137,37 +162,32 @@ class MainActivity : SimpleActivity(), FlingListener { } } } - - home_screen_grid.itemClickListener = { - performItemClick(it) - } - - home_screen_grid.itemLongClickListener = { - performItemLongClick(home_screen_grid.getClickableRect(it).left.toFloat(), it) - } } - private fun findFirstEmptyCell(): Rect? { + private fun findFirstEmptyCell(): Pair { val gridItems = homeScreenGridItemsDB.getAllItems() as ArrayList - val occupiedCells = 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(Pair(xCell, yCell)) + occupiedCells.add(Triple(item.page, xCell, yCell)) } } } - for (checkedYCell in 0 until COLUMN_COUNT) { - for (checkedXCell in 0 until ROW_COUNT - 1) { - val wantedCell = Pair(checkedXCell, checkedYCell) - if (!occupiedCells.contains(wantedCell)) { - return Rect(wantedCell.first, wantedCell.second, wantedCell.first, wantedCell.second) + 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 null + return Pair(maxPage + 1, Rect(0, 0, 0, 0)) } override fun onStart() { @@ -225,6 +245,10 @@ class MainActivity : SimpleActivity(), FlingListener { window.navigationBarColor = Color.TRANSPARENT } + home_screen_grid?.resizeGrid( + newRowCount = config.homeRowCount, + newColumnCount = config.homeColumnCount + ) (all_apps_fragment as? AllAppsFragment)?.onResume() } @@ -321,7 +345,7 @@ class MainActivity : SimpleActivity(), FlingListener { home_screen_grid.draggedItemMoved(event.x.toInt(), event.y.toInt()) } - if (mTouchDownY != -1 && !mIgnoreMoveEvents) { + if (hasFingerMoved && !mIgnoreMoveEvents) { val diffY = mTouchDownY - event.y if (isWidgetsFragmentExpanded()) { @@ -367,7 +391,7 @@ class MainActivity : SimpleActivity(), FlingListener { // 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) + ((Math.abs(mTouchDownX - event.x) > mMoveGestureThreshold) || (Math.abs(mTouchDownY - event.y) > mMoveGestureThreshold)) private fun refetchLaunchers() { val launchers = getAllAppLaunchers() @@ -443,11 +467,12 @@ class MainActivity : SimpleActivity(), FlingListener { }, ANIMATION_DURATION) } - fun homeScreenLongPressed(x: Float, y: Float) { + fun homeScreenLongPressed(eventX: Float, eventY: Float) { if (isAllAppsFragmentExpanded()) { return } + val (x, y) = home_screen_grid.intoViewSpaceCoords(eventX, eventY) mIgnoreMoveEvents = true val clickedGridItem = home_screen_grid.isClickingGridItem(x.toInt(), y.toInt()) if (clickedGridItem != null) { @@ -459,8 +484,9 @@ class MainActivity : SimpleActivity(), FlingListener { showMainLongPressMenu(x, y) } - fun homeScreenClicked(x: Float, y: Float) { + fun homeScreenClicked(eventX: Float, eventY: Float) { home_screen_grid.hideResizeLines() + val (x, y) = home_screen_grid.intoViewSpaceCoords(eventX, eventY) val clickedGridItem = home_screen_grid.isClickingGridItem(x.toInt(), y.toInt()) if (clickedGridItem != null) { performItemClick(clickedGridItem) @@ -499,7 +525,7 @@ class MainActivity : SimpleActivity(), FlingListener { mLongPressedIcon = gridItem val anchorY = if (isOnAllAppsFragment || gridItem.type == ITEM_TYPE_WIDGET) { y - } else if (gridItem.top == ROW_COUNT - 1) { + } else if (gridItem.top == config.homeRowCount - 1) { home_screen_grid.sideMargins.top + (gridItem.top * home_screen_grid.cellHeight.toFloat()) } else { (gridItem.top * home_screen_grid.cellHeight.toFloat()) @@ -648,11 +674,20 @@ class MainActivity : SimpleActivity(), FlingListener { return true } - if (velocityY > 0) { - flingListener.onFlingDown() - } else { - flingListener.onFlingUp() + if (abs(velocityY) > abs(velocityX)) { + if (velocityY > 0) { + flingListener.onFlingDown() + } else { + flingListener.onFlingUp() + } + } else if (abs(velocityX) > abs(velocityY)) { + if (velocityX > 0) { + flingListener.onFlingRight() + } else { + flingListener.onFlingLeft() + } } + return true } @@ -683,6 +718,16 @@ class MainActivity : SimpleActivity(), FlingListener { } } + override fun onFlingRight() { + mIgnoreUpEvent = true + home_screen_grid.prevPage(redraw = true) + } + + override fun onFlingLeft() { + mIgnoreUpEvent = true + home_screen_grid.nextPage(redraw = true) + } + @SuppressLint("WrongConstant") fun getAllAppLaunchers(): ArrayList { val hiddenIcons = hiddenIconsDB.getHiddenIcons().map { @@ -729,7 +774,24 @@ class MainActivity : SimpleActivity(), FlingListener { val defaultDialerPackage = (getSystemService(Context.TELECOM_SERVICE) as TelecomManager).defaultDialerPackage appLaunchers.firstOrNull { it.packageName == defaultDialerPackage }?.apply { val dialerIcon = - HomeScreenGridItem(null, 0, ROW_COUNT - 1, 0, ROW_COUNT - 1, defaultDialerPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null) + HomeScreenGridItem( + null, + 0, + config.homeRowCount - 1, + 0, + config.homeRowCount - 1, + 0, + defaultDialerPackage, + "", + title, + ITEM_TYPE_ICON, + "", + -1, + "", + "", + null, + true + ) homeScreenGridItems.add(dialerIcon) } } catch (e: Exception) { @@ -739,7 +801,24 @@ class MainActivity : SimpleActivity(), FlingListener { val defaultSMSMessengerPackage = Telephony.Sms.getDefaultSmsPackage(this) appLaunchers.firstOrNull { it.packageName == defaultSMSMessengerPackage }?.apply { val SMSMessengerIcon = - HomeScreenGridItem(null, 1, ROW_COUNT - 1, 1, ROW_COUNT - 1, defaultSMSMessengerPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null) + HomeScreenGridItem( + null, + 1, + config.homeRowCount - 1, + 1, + config.homeRowCount - 1, + 0, + defaultSMSMessengerPackage, + "", + title, + ITEM_TYPE_ICON, + "", + -1, + "", + "", + null, + true + ) homeScreenGridItems.add(SMSMessengerIcon) } } catch (e: Exception) { @@ -751,7 +830,24 @@ class MainActivity : SimpleActivity(), FlingListener { val defaultBrowserPackage = resolveInfo!!.activityInfo.packageName appLaunchers.firstOrNull { it.packageName == defaultBrowserPackage }?.apply { val browserIcon = - HomeScreenGridItem(null, 2, ROW_COUNT - 1, 2, ROW_COUNT - 1, defaultBrowserPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null) + HomeScreenGridItem( + null, + 2, + config.homeRowCount - 1, + 2, + config.homeRowCount - 1, + 0, + defaultBrowserPackage, + "", + title, + ITEM_TYPE_ICON, + "", + -1, + "", + "", + null, + true + ) homeScreenGridItems.add(browserIcon) } } catch (e: Exception) { @@ -762,7 +858,24 @@ class MainActivity : SimpleActivity(), FlingListener { val storePackage = potentialStores.firstOrNull { isPackageInstalled(it) && appLaunchers.map { it.packageName }.contains(it) } if (storePackage != null) { appLaunchers.firstOrNull { it.packageName == storePackage }?.apply { - val storeIcon = HomeScreenGridItem(null, 3, ROW_COUNT - 1, 3, ROW_COUNT - 1, storePackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null) + val storeIcon = HomeScreenGridItem( + null, + 3, + config.homeRowCount - 1, + 3, + config.homeRowCount - 1, + 0, + storePackage, + "", + title, + ITEM_TYPE_ICON, + "", + -1, + "", + "", + null, + true + ) homeScreenGridItems.add(storeIcon) } } @@ -775,7 +888,24 @@ class MainActivity : SimpleActivity(), FlingListener { val defaultCameraPackage = resolveInfo!!.activityInfo.packageName appLaunchers.firstOrNull { it.packageName == defaultCameraPackage }?.apply { val cameraIcon = - HomeScreenGridItem(null, 4, ROW_COUNT - 1, 4, ROW_COUNT - 1, defaultCameraPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null) + HomeScreenGridItem( + null, + 4, + config.homeRowCount - 1, + 4, + config.homeRowCount - 1, + 0, + defaultCameraPackage, + "", + title, + ITEM_TYPE_ICON, + "", + -1, + "", + "", + null, + true + ) homeScreenGridItems.add(cameraIcon) } } catch (e: Exception) { 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 4f6649a..f8bcabe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/activities/SettingsActivity.kt @@ -12,6 +12,9 @@ import com.simplemobiletools.launcher.BuildConfig import com.simplemobiletools.launcher.R import com.simplemobiletools.launcher.extensions.config import com.simplemobiletools.launcher.helpers.MAX_COLUMN_COUNT +import com.simplemobiletools.launcher.helpers.MAX_ROW_COUNT +import com.simplemobiletools.launcher.helpers.MIN_COLUMN_COUNT +import com.simplemobiletools.launcher.helpers.MIN_ROW_COUNT import kotlinx.android.synthetic.main.activity_settings.* import java.util.Locale import kotlin.system.exitProcess @@ -38,11 +41,13 @@ class SettingsActivity : SimpleActivity() { setupAlwaysNavigateToHomeScreen() setupDrawerColumnCount() setupDrawerSearchBar() + setupHomeRowCount() + setupHomeColumnCount() setupLanguage() setupManageHiddenIcons() updateTextColors(settings_holder) - arrayOf(settings_color_customization_section_label, settings_general_settings_label, settings_drawer_settings_label).forEach { + arrayOf(settings_color_customization_section_label, settings_general_settings_label, settings_drawer_settings_label, settings_home_screen_label).forEach { it.setTextColor(getProperPrimaryColor()) } } @@ -124,6 +129,44 @@ class SettingsActivity : SimpleActivity() { } } + private fun setupHomeRowCount() { + val currentRowCount = config.homeRowCount + settings_home_screen_row_count.text = currentRowCount.toString() + settings_home_screen_row_count_holder.setOnClickListener { + val items = ArrayList() + for (i in MIN_ROW_COUNT..MAX_ROW_COUNT) { + items.add(RadioItem(i, resources.getQuantityString(R.plurals.row_counts, i, i))) + } + + RadioGroupDialog(this, items, currentRowCount) { + val newRowCount = it as Int + if (currentRowCount != newRowCount) { + config.homeRowCount = newRowCount + setupHomeRowCount() + } + } + } + } + + private fun setupHomeColumnCount() { + val currentColumnCount = config.homeColumnCount + settings_home_screen_column_count.text = currentColumnCount.toString() + settings_home_screen_column_count_holder.setOnClickListener { + val items = ArrayList() + for (i in MIN_COLUMN_COUNT..MAX_COLUMN_COUNT) { + items.add(RadioItem(i, resources.getQuantityString(R.plurals.column_counts, i, i))) + } + + RadioGroupDialog(this, items, currentColumnCount) { + val newColumnCount = it as Int + if (currentColumnCount != newColumnCount) { + config.homeColumnCount = newColumnCount + setupHomeColumnCount() + } + } + } + } + private fun setupLanguage() { settings_language.text = Locale.getDefault().displayLanguage settings_language_holder.beVisibleIf(isTiramisuPlus()) 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 de46cc2..1754cd6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/databases/AppsDatabase.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/databases/AppsDatabase.kt @@ -15,7 +15,7 @@ import com.simplemobiletools.launcher.models.AppLauncher import com.simplemobiletools.launcher.models.HiddenIcon import com.simplemobiletools.launcher.models.HomeScreenGridItem -@Database(entities = [AppLauncher::class, HomeScreenGridItem::class, HiddenIcon::class], version = 4) +@Database(entities = [AppLauncher::class, HomeScreenGridItem::class, HiddenIcon::class], version = 5) @TypeConverters(Converters::class) abstract class AppsDatabase : RoomDatabase() { @@ -36,6 +36,7 @@ abstract class AppsDatabase : RoomDatabase() { .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4) + .addMigrations(MIGRATION_4_5) .build() } } @@ -64,5 +65,13 @@ abstract class AppsDatabase : RoomDatabase() { database.execSQL("CREATE UNIQUE INDEX `index_hidden_icons_id` ON `hidden_icons` (`id`)") } } + + private val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE home_screen_grid_items ADD COLUMN page INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE home_screen_grid_items ADD COLUMN docked INTEGER NOT NULL DEFAULT 0") + database.execSQL("UPDATE home_screen_grid_items SET docked = 1 WHERE page = 0 AND type != 1 AND top = 5") + } + } } } 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 fe4a5d5..ff2bf46 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/AllAppsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/AllAppsFragment.kt @@ -198,6 +198,7 @@ class AllAppsFragment(context: Context, attributeSet: AttributeSet) : MyFragment -1, -1, -1, + 0, appLauncher.packageName, appLauncher.activityName, appLauncher.title, @@ -207,6 +208,7 @@ class AllAppsFragment(context: Context, attributeSet: AttributeSet) : MyFragment "", "", null, + false, 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 189906a..1053144 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/WidgetsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/fragments/WidgetsFragment.kt @@ -18,6 +18,7 @@ import com.simplemobiletools.commons.helpers.isRPlus import com.simplemobiletools.launcher.R import com.simplemobiletools.launcher.activities.MainActivity import com.simplemobiletools.launcher.adapters.WidgetsAdapter +import com.simplemobiletools.launcher.extensions.config import com.simplemobiletools.launcher.extensions.getInitialCellSize import com.simplemobiletools.launcher.helpers.ITEM_TYPE_SHORTCUT import com.simplemobiletools.launcher.helpers.ITEM_TYPE_WIDGET @@ -238,6 +239,11 @@ class WidgetsFragment(context: Context, attributeSet: AttributeSet) : MyFragment } override fun onWidgetLongPressed(appWidget: AppWidget) { + if (appWidget.heightCells > context.config.homeRowCount - 1 || appWidget.widthCells > context.config.homeColumnCount) { + context.showErrorToast(context.getString(R.string.widget_too_big)) + return + } + val type = if (appWidget.isShortcut) { ITEM_TYPE_SHORTCUT } else { @@ -250,6 +256,7 @@ class WidgetsFragment(context: Context, attributeSet: AttributeSet) : MyFragment -1, -1, -1, + 0, appWidget.appPackageName, "", "", @@ -259,6 +266,7 @@ class WidgetsFragment(context: Context, attributeSet: AttributeSet) : MyFragment "", "", null, + false, appWidget.widgetPreviewImage, appWidget.providerInfo, appWidget.activityInfo, diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Config.kt index bc55404..e902b24 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Config.kt @@ -13,6 +13,14 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getBoolean(WAS_HOME_SCREEN_INIT, false) set(wasHomeScreenInit) = prefs.edit().putBoolean(WAS_HOME_SCREEN_INIT, wasHomeScreenInit).apply() + var homeColumnCount: Int + get() = prefs.getInt(HOME_COLUMN_COUNT, COLUMN_COUNT) + set(homeColumnCount) = prefs.edit().putInt(HOME_COLUMN_COUNT, homeColumnCount).apply() + + var homeRowCount: Int + get() = prefs.getInt(HOME_ROW_COUNT, ROW_COUNT) + set(homeRowCount) = prefs.edit().putInt(HOME_ROW_COUNT, homeRowCount).apply() + var drawerColumnCount: Int get() = prefs.getInt(DRAWER_COLUMN_COUNT, context.resources.getInteger(R.integer.portrait_column_count)) set(drawerColumnCount) = prefs.edit().putInt(DRAWER_COLUMN_COUNT, drawerColumnCount).apply() 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 d94e6e5..6b9f2a1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/helpers/Constants.kt @@ -5,6 +5,8 @@ const val WIDGET_LIST_ITEMS_HOLDER = 1 // shared prefs const val WAS_HOME_SCREEN_INIT = "was_home_screen_init" +const val HOME_ROW_COUNT = "home_row_count" +const val HOME_COLUMN_COUNT = "home_column_count" const val DRAWER_COLUMN_COUNT = "drawer_column_count" const val SHOW_SEARCH_BAR = "show_search_bar" const val ALWAYS_NAVIGATE_TO_HOME_SCREEN = "always_navigate_to_home_screen" @@ -12,6 +14,9 @@ const val ALWAYS_NAVIGATE_TO_HOME_SCREEN = "always_navigate_to_home_screen" // default home screen grid size const val ROW_COUNT = 6 const val COLUMN_COUNT = 5 +const val MIN_ROW_COUNT = 2 +const val MAX_ROW_COUNT = 15 +const val MIN_COLUMN_COUNT = 2 const val MAX_COLUMN_COUNT = 15 const val UNINSTALL_APP_REQUEST_CODE = 50 diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/FlingListener.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/FlingListener.kt index 3b5dd09..b9dd60e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/FlingListener.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/FlingListener.kt @@ -4,4 +4,8 @@ interface FlingListener { fun onFlingUp() fun onFlingDown() + + fun onFlingRight() + + fun onFlingLeft() } 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 1eda528..592e895 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/interfaces/HomeScreenGridItemsDao.kt @@ -23,8 +23,8 @@ 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 WHERE id = :id") - fun updateItemPosition(left: Int, top: Int, right: Int, bottom: Int, id: Long) + @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("DELETE FROM home_screen_grid_items WHERE id = :id") fun deleteById(id: Long) 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 935f1ed..c87acab 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/models/HomeScreenGridItem.kt @@ -15,6 +15,7 @@ data class HomeScreenGridItem( @ColumnInfo(name = "top") var top: Int, @ColumnInfo(name = "right") var right: Int, @ColumnInfo(name = "bottom") var bottom: Int, + @ColumnInfo(name = "page") var page: Int, @ColumnInfo(name = "package_name") var packageName: String, @ColumnInfo(name = "activity_name") var activityName: String, // needed at apps that create multiple icons at install, not just the launcher @ColumnInfo(name = "title") var title: String, @@ -24,6 +25,7 @@ data class HomeScreenGridItem( @ColumnInfo(name = "intent") var intent: String, // used at static and dynamic shortcuts on click @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 @Ignore var drawable: Drawable? = null, @Ignore var providerInfo: AppWidgetProviderInfo? = null, // used at widgets @@ -31,7 +33,7 @@ data class HomeScreenGridItem( @Ignore var widthCells: Int = 1, @Ignore var heightCells: Int = 1 ) { - constructor() : this(null, -1, -1, -1, -1, "", "", "", ITEM_TYPE_ICON, "", -1, "", "", null, null, null, null, 1, 1) + constructor() : this(null, -1, -1, -1, -1, 0, "", "", "", ITEM_TYPE_ICON, "", -1, "", "", null, false, null, null, null, 1, 1) fun getWidthInCells() = if (right == -1 || left == -1) { widthCells @@ -45,5 +47,21 @@ data class HomeScreenGridItem( bottom - top + 1 } + fun getDockAdjustedTop(rowCount: Int): Int { + return if (!docked) { + top + } else { + rowCount - 1 + } + } + + fun getDockAdjustedBottom(rowCount: Int): Int { + return if (!docked) { + bottom + } else { + rowCount - 1 + } + } + fun getItemIdentifier() = "$packageName/$activityName" } 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 d87a4fd..9373c11 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/HomeScreenGrid.kt @@ -1,5 +1,8 @@ package com.simplemobiletools.launcher.views +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator import android.annotation.SuppressLint import android.appwidget.AppWidgetHostView import android.appwidget.AppWidgetManager @@ -21,35 +24,57 @@ import androidx.core.graphics.drawable.toDrawable import androidx.core.view.ViewCompat 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.launcher.R import com.simplemobiletools.launcher.activities.MainActivity +import com.simplemobiletools.launcher.extensions.config import com.simplemobiletools.launcher.extensions.getDrawableForPackageName import com.simplemobiletools.launcher.extensions.homeScreenGridItemsDB import com.simplemobiletools.launcher.helpers.* import com.simplemobiletools.launcher.models.HomeScreenGridItem import kotlinx.android.synthetic.main.activity_main.view.* +import kotlin.math.abs +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : RelativeLayout(context, attrs, defStyle) { constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) - private var iconMargin = context.resources.getDimension(R.dimen.icon_side_margin).toInt() + private var columnCount = context.config.homeColumnCount + private var rowCount = context.config.homeRowCount + private var cellXCoords = ArrayList(columnCount) + private var cellYCoords = ArrayList(rowCount) + 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(R.dimen.small_margin).toInt() private var roundedCornerRadius = context.resources.getDimension(R.dimen.activity_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 dragShadowCirclePaint: Paint + private var emptyPageIndicatorPaint: Paint + private var currentPageIndicatorPaint: Paint private var draggedItem: HomeScreenGridItem? = null private var resizedWidget: HomeScreenGridItem? = null private var isFirstDraw = true + private var redrawWidgets = false private var iconSize = 0 - // let's use a 6x5 grid for now with 1 special row at the bottom, prefilled with default apps - private var cellXCoords = ArrayList(COLUMN_COUNT) - private var cellYCoords = ArrayList(ROW_COUNT) - var cellWidth = 0 - var cellHeight = 0 + private var lastPage = 0 + private var currentPage = 0 + private var pageChangeLastArea = PageChangeArea.MIDDLE + private var pageChangeLastAreaEntryTime = 0L + private var pageChangeAnimLeftPercentage = 0f + private var pageChangeEnabled = true + private var pageChangeIndicatorsAlpha = 0f // apply fake margins at the home screen. Real ones would cause the icons be cut at dragging at screen sides var sideMargins = Rect() @@ -65,6 +90,27 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel var itemClickListener: ((HomeScreenGridItem) -> Unit)? = null var itemLongClickListener: ((HomeScreenGridItem) -> Unit)? = null + private val checkAndExecuteDelayedPageChange: Runnable = Runnable { + if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) { + when (pageChangeLastArea) { + PageChangeArea.RIGHT -> nextOrAdditionalPage(true) + PageChangeArea.LEFT -> prevPage(true) + else -> clearPageChangeFlags() + } + } + } + + private val startFadingIndicators: Runnable = Runnable { + ValueAnimator.ofFloat(1f, 0f) + .apply { + addUpdateListener { + pageChangeIndicatorsAlpha = it.animatedValue as Float + redrawGrid() + } + start() + } + } + init { ViewCompat.setAccessibilityDelegate(this, HomeScreenGridTouchHelper(this)) @@ -75,11 +121,19 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } dragShadowCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = context.resources.getColor(R.color.light_grey_stroke) + color = context.resources.getColor(R.color.hint_white) strokeWidth = context.resources.getDimension(R.dimen.small_margin) style = Paint.Style.STROKE } + emptyPageIndicatorPaint = Paint(dragShadowCirclePaint).apply { + strokeWidth = context.resources.getDimension(R.dimen.page_indicator_stroke_width) + } + currentPageIndicatorPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.resources.getColor(R.color.white) + style = Paint.Style.FILL + } + val sideMargin = context.resources.getDimension(R.dimen.normal_margin).toInt() sideMargins.apply { top = context.statusBarHeight @@ -115,6 +169,19 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + fun resizeGrid(newRowCount: Int, newColumnCount: Int) { + if (columnCount != newColumnCount || rowCount != newRowCount) { + rowCount = newRowCount + columnCount = newColumnCount + cellXCoords = ArrayList(columnCount) + cellYCoords = ArrayList(rowCount) + gridCenters.clear() + iconMargin = (context.resources.getDimension(R.dimen.icon_side_margin) * 5 / columnCount).toInt() + redrawWidgets = true + redrawGrid() + } + } + fun removeAppIcon(item: HomeScreenGridItem) { ensureBackgroundThread { removeItemFromHomeScreen(item) @@ -123,6 +190,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } gridItems.removeIf { it.id == item.id } + if (currentPage > getMaxPage()) { + post { + prevPage() + } + } redrawGrid() } } @@ -153,6 +225,8 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return } + pageChangeIndicatorsAlpha = 1f + removeCallbacks(startFadingIndicators) if (draggedItemCurrentCoords.first == -1 && draggedItemCurrentCoords.second == -1 && draggedItem != null) { if (draggedItem!!.type == ITEM_TYPE_WIDGET) { val draggedWidgetView = widgetViews.firstOrNull { it.tag == draggedItem?.widgetId } @@ -165,9 +239,47 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } draggedItemCurrentCoords = Pair(x, y) + if (x > right - sideMargins.right - cellWidth / 2) { + doWithPageChangeDelay(PageChangeArea.RIGHT) { + nextOrAdditionalPage() + } + } else if (x < left + sideMargins.left + cellWidth / 2) { + doWithPageChangeDelay(PageChangeArea.LEFT) { + prevPage() + } + } else { + clearPageChangeFlags() + } redrawGrid() } + private fun clearPageChangeFlags() { + pageChangeLastArea = PageChangeArea.MIDDLE + pageChangeLastAreaEntryTime = 0 + removeCallbacks(checkAndExecuteDelayedPageChange) + } + + private fun schedulePageChange() { + pageChangeLastAreaEntryTime = System.currentTimeMillis() + postDelayed(checkAndExecuteDelayedPageChange, PAGE_CHANGE_HOLD_THRESHOLD) + } + + private fun scheduleIndicatorsFade() { + pageChangeIndicatorsAlpha = 1f + postDelayed(startFadingIndicators, PAGE_INDICATORS_FADE_DELAY) + } + + private fun doWithPageChangeDelay(needed: PageChangeArea, pageChangeFunction: () -> Boolean) { + if (pageChangeLastArea != needed) { + pageChangeLastArea = needed + schedulePageChange() + } else if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) { + if (pageChangeFunction()) { + clearPageChangeFlags() + } + } + } + // figure out at which cell was the item dropped, if it is empty fun itemDraggingStopped() { widgetViews.forEach { @@ -178,6 +290,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return } + scheduleIndicatorsFade() when (draggedItem!!.type) { ITEM_TYPE_ICON, ITEM_TYPE_SHORTCUT -> addAppIconOrShortcut() ITEM_TYPE_WIDGET -> addWidget() @@ -207,10 +320,14 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel item.left = cellsRect.left item.top = cellsRect.top item.right = cellsRect.right - item.bottom = cellsRect.bottom + item.bottom = if (cellsRect.bottom > rowCount - 2) { + rowCount - 2 + } else { + cellsRect.bottom + } updateWidgetPositionAndSize(widgetView, item) ensureBackgroundThread { - context.homeScreenGridItemsDB.updateItemPosition(cellsRect.left, cellsRect.top, cellsRect.right, cellsRect.bottom, item.id!!) + context.homeScreenGridItemsDB.updateItemPosition(item.left, item.top, item.right, item.bottom, item.page, false, item.id!!) } } @@ -249,9 +366,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel // check if the destination cell is empty var areAllCellsEmpty = true val wantedCell = Pair(xIndex, yIndex) - gridItems.forEach { item -> + gridItems.filter { it.page == currentPage || it.docked }.forEach { item -> for (xCell in item.left..item.right) { - for (yCell in item.top..item.bottom) { + for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) val isAnyCellOccupied = wantedCell == cell if (isAnyCellOccupied) { @@ -272,9 +389,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel top = yIndex right = xIndex bottom = yIndex + page = currentPage + docked = yIndex == rowCount - 1 ensureBackgroundThread { - context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, id!!) + context.homeScreenGridItemsDB.updateItemPosition(left, top, right, bottom, page, docked, id!!) } } redrawIcons = true @@ -286,6 +405,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel yIndex, xIndex, yIndex, + currentPage, draggedItem!!.packageName, draggedItem!!.activityName, draggedItem!!.title, @@ -295,6 +415,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel "", "", draggedItem!!.icon, + yIndex == rowCount - 1, draggedItem!!.drawable, draggedItem!!.providerInfo, draggedItem!!.activityInfo @@ -352,9 +473,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } var areAllCellsEmpty = true - gridItems.filter { it.id != draggedItem?.id }.forEach { item -> + gridItems.filter { it.id != draggedItem?.id && (it.page == currentPage || it.docked) }.forEach { item -> for (xCell in item.left..item.right) { - for (yCell in item.top..item.bottom) { + for (yCell in item.getDockAdjustedTop(rowCount)..item.getDockAdjustedBottom(rowCount)) { val cell = Pair(xCell, yCell) val isAnyCellOccupied = widgetTargetCells.contains(cell) if (isAnyCellOccupied) { @@ -372,6 +493,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel top = widgetRect.top right = widgetRect.right bottom = widgetRect.bottom + page = currentPage } ensureBackgroundThread { @@ -383,9 +505,17 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel bindWidget(widgetItem, false) } } else { - context.homeScreenGridItemsDB.updateItemPosition(widgetItem.left, widgetItem.top, widgetItem.right, widgetItem.bottom, widgetItem.id!!) + context.homeScreenGridItemsDB.updateItemPosition( + widgetItem.left, + widgetItem.top, + widgetItem.right, + widgetItem.bottom, + currentPage, + false, + widgetItem.id!! + ) val widgetView = widgetViews.firstOrNull { it.tag == widgetItem.widgetId } - if (widgetView != null) { + if (widgetView != null && !widgetItem.outOfBounds()) { post { widgetView.x = calculateWidgetX(widgetItem.left) widgetView.y = calculateWidgetY(widgetItem.top) @@ -398,6 +528,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel right = widgetItem.right top = widgetItem.top bottom = widgetItem.bottom + page = widgetItem.page } } } @@ -437,6 +568,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } else { removeItemFromHomeScreen(item) } + + if (currentPage > getMaxPage()) { + prevPage(redraw = true) + } } } } @@ -469,7 +604,26 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun updateWidgetPositionAndSize(widgetView: AppWidgetHostView, item: HomeScreenGridItem): Size { - widgetView.x = calculateWidgetX(item.left) + var x = calculateWidgetX(item.left) + width * item.page - width * lastPage + if (pageChangeAnimLeftPercentage > 0f && pageChangeAnimLeftPercentage < 1f && (item.page == currentPage || item.page == lastPage)) { + val xFactor = if (currentPage > lastPage) { + pageChangeAnimLeftPercentage + } else { + -pageChangeAnimLeftPercentage + } + val lastXFactor = if (currentPage > lastPage) { + pageChangeAnimLeftPercentage - 1 + } else { + 1 - pageChangeAnimLeftPercentage + } + if (item.page == currentPage) { + x += width * xFactor + } + if (item.page == lastPage) { + x += width * lastXFactor + } + } + widgetView.x = x widgetView.y = calculateWidgetY(item.top) val widgetWidth = item.getWidthInCells() * cellWidth val widgetHeight = item.getHeightInCells() * cellHeight @@ -490,9 +644,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return Size(widgetWidth, widgetHeight) } - private fun calculateWidgetX(leftCell: Int) = leftCell * cellWidth + sideMargins.left.toFloat() + private fun calculateWidgetX(leftCell: Int) = cellXCoords[leftCell] + sideMargins.left.toFloat() + extraXMargin - private fun calculateWidgetY(topCell: Int) = topCell * cellHeight + sideMargins.top.toFloat() + private fun calculateWidgetY(topCell: Int) = cellYCoords[topCell] + sideMargins.top.toFloat() + extraYMargin // convert stuff like 102x192 to grid cells like 0x1 private fun getClosestGridCells(center: Pair): Pair? { @@ -529,21 +683,32 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fillCellSizes() } - gridItems.filter { it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT }.forEach { item -> - if (item.id != draggedItem?.id) { - val drawableX = cellXCoords[item.left] + iconMargin + sideMargins.left + val currentXFactor = if (currentPage > lastPage) { + pageChangeAnimLeftPercentage + } else { + -pageChangeAnimLeftPercentage + } + val lastXFactor = if (currentPage > lastPage) { + pageChangeAnimLeftPercentage - 1 + } else { + 1 - pageChangeAnimLeftPercentage + } + + fun handleItemDrawing(item: HomeScreenGridItem, xFactor: Float) { + if (item.id != draggedItem?.id) { + val drawableX = cellXCoords[item.left] + iconMargin + extraXMargin + sideMargins.left + (width * xFactor).toInt() + + if (item.docked) { + val drawableY = cellYCoords[rowCount - 1] + cellHeight - iconMargin - iconSize + sideMargins.top - // icons at the bottom are drawn at the bottom of the grid and they have no label - if (item.top == ROW_COUNT - 1) { - val drawableY = cellYCoords[item.top] + cellHeight - iconSize - iconMargin * 2 + sideMargins.top item.drawable!!.setBounds(drawableX, drawableY, drawableX + iconSize, drawableY + iconSize) } else { - val drawableY = cellYCoords[item.top] + iconSize / 2 + sideMargins.top + val drawableY = cellYCoords[item.top] + iconMargin + extraYMargin + sideMargins.top item.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 - val textY = cellYCoords[item.top] + iconSize * 1.5f + labelSideMargin + sideMargins.top + val textX = cellXCoords[item.left].toFloat() + labelSideMargin + sideMargins.left + width * xFactor + val textY = cellYCoords[item.top].toFloat() + iconSize + iconMargin + extraYMargin + labelSideMargin + sideMargins.top val staticLayout = StaticLayout.Builder .obtain(item.title, 0, item.title.length, textPaint, cellWidth - 2 * labelSideMargin) .setMaxLines(2) @@ -562,29 +727,93 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } } + gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.page == currentPage && !it.docked } + .forEach { item -> + if (item.outOfBounds()) { + return@forEach + } + + 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 + } + + 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 } + .forEach { item -> + if (item.outOfBounds()) { + return@forEach + } + + handleItemDrawing(item, lastXFactor) + } + } + if (isFirstDraw) { - gridItems.filter { it.type == ITEM_TYPE_WIDGET }.forEach { item -> + gridItems.filter { it.type == ITEM_TYPE_WIDGET && !it.outOfBounds() }.forEach { item -> bindWidget(item, true) } + } else { + gridItems.filter { it.type == ITEM_TYPE_WIDGET && !it.outOfBounds() }.forEach { item -> + widgetViews.firstOrNull { it.tag == item.widgetId }?.also { + updateWidgetPositionAndSize(it, item) + } + } + } + + // Only draw page indicators when there is a need for it + if (pageChangeAnimLeftPercentage > 0f || pageChangeIndicatorsAlpha != 0f) { + val pageCount = max(getMaxPage(), currentPage) + 1 + val pageIndicatorsRequiredWidth = pageCount * pageIndicatorRadius * 2 + pageCount * (pageIndicatorMargin - 1) + val usableWidth = getFakeWidth() + val pageIndicatorsStart = (usableWidth - pageIndicatorsRequiredWidth) / 2 + sideMargins.left + var currentPageIndicatorLeft = pageIndicatorsStart + val pageIndicatorY = cellYCoords[rowCount - 1].toFloat() + sideMargins.top + extraYMargin + iconMargin + val pageIndicatorStep = pageIndicatorRadius * 2 + pageIndicatorMargin + if (pageChangeIndicatorsAlpha != 0f) { + emptyPageIndicatorPaint.alpha = (pageChangeIndicatorsAlpha * 255.0f).toInt() + } else { + emptyPageIndicatorPaint.alpha = 255 + } + // Draw empty page indicators + for (page in 0 until pageCount) { + canvas.drawCircle(currentPageIndicatorLeft + pageIndicatorRadius, pageIndicatorY, pageIndicatorRadius, emptyPageIndicatorPaint) + currentPageIndicatorLeft += pageIndicatorStep + } + + // Draw current page indicator on exact position + val currentIndicatorRangeStart = pageIndicatorsStart + lastPage * pageIndicatorStep + val currentIndicatorRangeEnd = pageIndicatorsStart + currentPage * pageIndicatorStep + val currentIndicatorPosition = MathUtils.lerp(currentIndicatorRangeStart, currentIndicatorRangeEnd, 1 - pageChangeAnimLeftPercentage) + if (pageChangeIndicatorsAlpha != 0f) { + currentPageIndicatorPaint.alpha = (pageChangeIndicatorsAlpha * 255.0f).toInt() + } else { + currentPageIndicatorPaint.alpha = 255 + } + canvas.drawCircle(currentIndicatorPosition + pageIndicatorRadius, pageIndicatorY, pageIndicatorRadius, currentPageIndicatorPaint) } if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT) { // draw a circle under the current cell val center = gridCenters.minBy { - Math.abs(it.first - draggedItemCurrentCoords.first + sideMargins.left) + Math.abs(it.second - draggedItemCurrentCoords.second + sideMargins.top) + 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.toFloat() + iconSize / 2 + sideMargins.left - val shadowY = if (gridCells.second == ROW_COUNT - 1) { - cellYCoords[gridCells.second] + cellHeight - iconSize / 2 - iconMargin * 2 + 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] + iconSize + cellYCoords[gridCells.second] + iconMargin + iconSize / 2f + extraYMargin } + sideMargins.top - canvas.drawCircle(shadowX, shadowY.toFloat(), iconSize / 2f, dragShadowCirclePaint) + 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 @@ -602,10 +831,10 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel val gridCells = getClosestGridCells(center) if (gridCells != null) { val widgetRect = getWidgetOccupiedRect(gridCells) - val leftSide = widgetRect.left * cellWidth + sideMargins.left + iconMargin.toFloat() - val topSide = widgetRect.top * cellHeight + sideMargins.top + iconMargin.toFloat() - val rightSide = leftSide + draggedItem!!.getWidthInCells() * cellWidth - sideMargins.right - iconMargin.toFloat() - val bottomSide = topSide + draggedItem!!.getHeightInCells() * cellHeight - sideMargins.top + val leftSide = calculateWidgetX(widgetRect.left) + val topSide = calculateWidgetY(widgetRect.top) + val rightSide = leftSide + draggedItem!!.getWidthInCells() * cellWidth + val bottomSide = topSide + draggedItem!!.getHeightInCells() * cellHeight canvas.drawRoundRect(leftSide, topSide, rightSide, bottomSide, roundedCornerRadius, roundedCornerRadius, dragShadowCirclePaint) } @@ -630,14 +859,24 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } private fun fillCellSizes() { - cellWidth = getFakeWidth() / COLUMN_COUNT - cellHeight = getFakeHeight() / ROW_COUNT - iconSize = cellWidth - 2 * iconMargin - for (i in 0 until COLUMN_COUNT) { + cellWidth = getFakeWidth() / context.config.homeColumnCount + cellHeight = getFakeHeight() / context.config.homeRowCount + extraXMargin = if (cellWidth > cellHeight) { + (cellWidth - cellHeight) / 2 + } else { + 0 + } + extraYMargin = if (cellHeight > cellWidth) { + (cellHeight - cellWidth) / 2 + } else { + 0 + } + iconSize = min(cellWidth, cellHeight) - 2 * iconMargin + for (i in 0 until context.config.homeColumnCount) { cellXCoords.add(i, i * cellWidth) } - for (i in 0 until ROW_COUNT) { + for (i in 0 until context.config.homeRowCount) { cellYCoords.add(i, i * cellHeight) } @@ -666,27 +905,33 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel fillCellSizes() } - val clickableLeft = item.left * cellWidth + sideMargins.left - val clickableTop = cellYCoords[item.top] + iconSize / 3 + sideMargins.top - return Rect(clickableLeft, clickableTop, clickableLeft + cellWidth, clickableTop + iconSize * 2) + val clickableLeft = cellXCoords[item.left] + sideMargins.left + extraXMargin + val 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) } // drag the center of the widget, not the top left corner private fun getWidgetOccupiedRect(item: Pair): Rect { - val left = item.first - Math.floor((draggedItem!!.getWidthInCells() - 1) / 2.0).toInt() + 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) if (rect.left < 0) { rect.right -= rect.left rect.left = 0 - } else if (rect.right > COLUMN_COUNT - 1) { - val diff = rect.right - COLUMN_COUNT + 1 + } else if (rect.right > columnCount - 1) { + val diff = rect.right - columnCount + 1 rect.right -= diff rect.left -= diff } - // do not allow placing widgets at the bottom row, that is for pinned default apps - if (rect.bottom >= ROW_COUNT - 1) { - val diff = rect.bottom - ROW_COUNT + 2 + if (rect.top < 0) { + rect.bottom -= rect.top + rect.top = 0 + } else if (rect.bottom > rowCount - 2) { + val diff = rect.bottom - rowCount + 2 rect.bottom -= diff rect.top -= diff } @@ -695,7 +940,11 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } fun isClickingGridItem(x: Int, y: Int): HomeScreenGridItem? { - for (gridItem in gridItems) { + for (gridItem in gridItems.filter { it.page == currentPage || it.docked }) { + if (gridItem.outOfBounds()) { + continue + } + if (gridItem.type == ITEM_TYPE_ICON || gridItem.type == ITEM_TYPE_SHORTCUT) { val rect = getClickableRect(gridItem) if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { @@ -716,6 +965,18 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return null } + fun intoViewSpaceCoords(screenSpaceX: Float, screenSpaceY: Float): Pair { + val viewLocation = IntArray(2) + getLocationOnScreen(viewLocation) + val x = screenSpaceX - viewLocation[0] + val y = screenSpaceY - viewLocation[1] + return Pair(x, y) + } + + private fun HomeScreenGridItem.outOfBounds(): Boolean { + return (left >= cellXCoords.size || right >= cellXCoords.size || (!docked && (top >= cellYCoords.size - 1 || bottom >= cellYCoords.size - 1))) + } + private inner class HomeScreenGridTouchHelper(host: View) : ExploreByTouchHelper(host) { override fun getVirtualViewAt(x: Float, y: Float): Int { val item = isClickingGridItem(x.toInt(), y.toInt()) @@ -728,7 +989,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel } override fun getVisibleVirtualViews(virtualViewIds: MutableList?) { - val sorted = gridItems.sortedBy { it.top * 100 + it.left } + val sorted = gridItems.sortedBy { + it.getDockAdjustedTop(rowCount) * 100 + it.left + } sorted.forEachIndexed { index, homeScreenGridItem -> virtualViewIds?.add(index, homeScreenGridItem.id?.toInt() ?: index) } @@ -769,6 +1032,90 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel return false } + } + private fun getMaxPage() = gridItems.filter { !it.docked && !it.outOfBounds() }.maxOfOrNull { it.page } ?: 0 + + private fun nextOrAdditionalPage(redraw: Boolean = false): Boolean { + if (currentPage < getMaxPage() + 1 && pageChangeEnabled) { + lastPage = currentPage + currentPage++ + handlePageChange(redraw) + return true + } + + return false + } + + fun nextPage(redraw: Boolean = false): Boolean { + if (currentPage < getMaxPage() && pageChangeEnabled) { + lastPage = currentPage + currentPage++ + handlePageChange(redraw) + return true + } + + return false + } + + fun prevPage(redraw: Boolean = false): Boolean { + if (currentPage > 0 && pageChangeEnabled) { + lastPage = currentPage + currentPage-- + handlePageChange(redraw) + return true + } + + return false + } + + fun skipToPage(targetPage: Int): Boolean { + if (currentPage != targetPage && targetPage < getMaxPage() + 1) { + lastPage = currentPage + currentPage = targetPage + handlePageChange() + return true + } + + return false + } + + private fun handlePageChange(redraw: Boolean = false) { + pageChangeEnabled = false + pageChangeIndicatorsAlpha = 0f + removeCallbacks(startFadingIndicators) + if (redraw) { + redrawGrid() + } + ValueAnimator.ofFloat(1f, 0f) + .apply { + addUpdateListener { + pageChangeAnimLeftPercentage = it.animatedValue as Float + redrawGrid() + } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + pageChangeAnimLeftPercentage = 0f + pageChangeEnabled = true + lastPage = currentPage + schedulePageChange() + scheduleIndicatorsFade() + redrawGrid() + } + }) + start() + } + } + + companion object { + private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L + private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L + + private enum class PageChangeArea { + LEFT, + MIDDLE, + RIGHT + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/launcher/views/MyAppWidgetResizeFrame.kt b/app/src/main/kotlin/com/simplemobiletools/launcher/views/MyAppWidgetResizeFrame.kt index 40f3f31..b8ad846 100644 --- a/app/src/main/kotlin/com/simplemobiletools/launcher/views/MyAppWidgetResizeFrame.kt +++ b/app/src/main/kotlin/com/simplemobiletools/launcher/views/MyAppWidgetResizeFrame.kt @@ -9,10 +9,9 @@ import android.util.AttributeSet import android.view.MotionEvent import android.widget.RelativeLayout import com.simplemobiletools.launcher.R +import com.simplemobiletools.launcher.extensions.config import com.simplemobiletools.launcher.extensions.getCellCount -import com.simplemobiletools.launcher.helpers.COLUMN_COUNT import com.simplemobiletools.launcher.helpers.MAX_CLICK_DURATION -import com.simplemobiletools.launcher.helpers.ROW_COUNT import com.simplemobiletools.launcher.models.HomeScreenGridItem @SuppressLint("ViewConstructor") @@ -76,8 +75,8 @@ class MyAppWidgetResizeFrame(context: Context, attrs: AttributeSet, defStyle: In it.provider.className == gridItem.className } ?: return - minResizeWidthCells = Math.min(COLUMN_COUNT, context.getCellCount(providerInfo.minResizeWidth)) - minResizeHeightCells = Math.min(ROW_COUNT, context.getCellCount(providerInfo.minResizeHeight)) + minResizeWidthCells = Math.min(context.config.homeColumnCount, context.getCellCount(providerInfo.minResizeWidth)) + minResizeHeightCells = Math.min(context.config.homeRowCount, context.getCellCount(providerInfo.minResizeHeight)) redrawFrame() occupiedCells.clear() @@ -224,7 +223,7 @@ class MyAppWidgetResizeFrame(context: Context, attrs: AttributeSet, defStyle: In } } - if (wantedBottomCellY == ROW_COUNT - 1) { + if (wantedBottomCellY == context.config.homeRowCount - 1) { areAllCellsFree = false } @@ -322,7 +321,7 @@ class MyAppWidgetResizeFrame(context: Context, attrs: AttributeSet, defStyle: In } } - if (wantedBottomCellY == ROW_COUNT - 1) { + if (wantedBottomCellY == context.config.homeRowCount - 1) { areAllCellsFree = false } diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index a47f5e8..e33a741 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -191,7 +191,7 @@ style="@style/SettingsHolderTextViewOneLinerStyle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/ripple_bottom_corners"> + android:background="@drawable/ripple_background"> + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2ced7a2..6450143 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -13,6 +13,8 @@ لا يمكن إلغاء تثبيت بعض التطبيقات بسبب قيود النظام، ولكن يمكن إخفاء أيقوناتها لتجنب ظهورها. درج التطبيق Always navigate to home screen on home button + Home screen + Widget is too big for current home screen size