summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Sierd de Vries <contact@lecapuchon.nl>2026-08-12 15:39:34 +0200
committerDirk Sierd de Vries <contact@lecapuchon.nl>2026-08-12 15:39:34 +0200
commit1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad (patch)
treed6bd764f0a5a53533d8d90877a368c0c42c3cea2
parent2dcff5bc664a00b61283a95ebfdfb5717f626d94 (diff)
downloadSidePhOnelauncher-1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad.tar.gz
SidePhOnelauncher-1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad.tar.bz2
SidePhOnelauncher-1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad.zip
Add configurable focus indicator styles
-rw-r--r--app/src/main/java/app/sidephonelauncher/MainActivity.kt56
-rw-r--r--app/src/main/java/app/sidephonelauncher/data/Constants.kt10
-rw-r--r--app/src/main/java/app/sidephonelauncher/data/Prefs.kt7
-rw-r--r--app/src/main/java/app/sidephonelauncher/helper/Extensions.kt34
-rw-r--r--app/src/main/java/app/sidephonelauncher/helper/Utils.kt28
-rw-r--r--app/src/main/java/app/sidephonelauncher/ui/AppDrawerAdapter.kt46
-rw-r--r--app/src/main/java/app/sidephonelauncher/ui/AppDrawerFragment.kt14
-rw-r--r--app/src/main/java/app/sidephonelauncher/ui/HomeAppTextView.kt30
-rw-r--r--app/src/main/java/app/sidephonelauncher/ui/HomeFragment.kt29
-rw-r--r--app/src/main/java/app/sidephonelauncher/ui/SettingsFragment.kt40
-rw-r--r--app/src/main/res/layout/adapter_app_drawer.xml9
-rw-r--r--app/src/main/res/layout/fragment_settings.xml68
-rw-r--r--app/src/main/res/values-night/styles.xml14
-rw-r--r--app/src/main/res/values/strings.xml3
-rw-r--r--app/src/main/res/values/styles.xml14
-rw-r--r--gradle.properties1
16 files changed, 304 insertions, 99 deletions
diff --git a/app/src/main/java/app/sidephonelauncher/MainActivity.kt b/app/src/main/java/app/sidephonelauncher/MainActivity.kt
index 2e86023..cc2621e 100644
--- a/app/src/main/java/app/sidephonelauncher/MainActivity.kt
+++ b/app/src/main/java/app/sidephonelauncher/MainActivity.kt
@@ -15,6 +15,7 @@ import android.view.KeyEvent
import android.view.View
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
import androidx.activity.OnBackPressedCallback
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.ViewModelProvider
@@ -37,11 +38,11 @@ import app.sidephonelauncher.helper.isEinkDisplay
import app.sidephonelauncher.helper.isSidePhOnelauncherDefault
import app.sidephonelauncher.helper.isTablet
import app.sidephonelauncher.helper.openUrl
+import app.sidephonelauncher.helper.createLauncherSelectorIntent
import app.sidephonelauncher.helper.rateApp
import app.sidephonelauncher.helper.resetLauncherViaFakeActivity
import app.sidephonelauncher.helper.setPlainWallpaper
import app.sidephonelauncher.helper.shareApp
-import app.sidephonelauncher.helper.showLauncherSelector
import app.sidephonelauncher.helper.showToast
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -58,6 +59,11 @@ class MainActivity : AppCompatActivity() {
private var isResumed = false
private var profileReceiver: BroadcastReceiver? = null
private var screenStateReceiver: BroadcastReceiver? = null
+ private val launcherSelectorLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == Activity.RESULT_OK) {
+ resetLauncherViaFakeActivity()
+ }
+ }
// override fun onBackPressed() {
// if (navController.currentDestination?.id != R.id.mainFragment)
@@ -153,6 +159,7 @@ class MainActivity : AppCompatActivity() {
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_ENTER -> true
+
else -> false
}
if (dpadKey) {
@@ -231,10 +238,13 @@ class MainActivity : AppCompatActivity() {
openLauncherChooser(it)
}
viewModel.resetLauncherLiveData.observe(this) {
- if (isDefaultLauncher() || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
+ if (isDefaultLauncher() || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
resetLauncherViaFakeActivity()
- else
- showLauncherSelector(Constants.REQUEST_CODE_LAUNCHER_SELECTOR)
+ } else {
+ createLauncherSelectorIntent()?.let { intent ->
+ launcherSelectorLauncher.launch(intent)
+ }
+ }
}
viewModel.checkForMessages.observe(this) {
checkForMessages()
@@ -242,7 +252,11 @@ class MainActivity : AppCompatActivity() {
viewModel.showDialog.observe(this) {
when (it) {
Constants.Dialog.ABOUT -> {
- showMessageDialog(R.string.app_name, R.string.welcome_to_sidephonelauncher_settings, R.string.okay) {
+ showMessageDialog(
+ R.string.app_name,
+ R.string.welcome_to_sidephonelauncher_settings,
+ R.string.okay
+ ) {
binding.messageLayout.visibility = View.GONE
}
}
@@ -300,7 +314,11 @@ class MainActivity : AppCompatActivity() {
}
Constants.Dialog.NOTIFICATION_DOTS -> {
- showMessageDialog(R.string.notification_dots, R.string.notification_dots_message, R.string.permission) {
+ showMessageDialog(
+ R.string.notification_dots,
+ R.string.notification_dots_message,
+ R.string.permission
+ ) {
try {
startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
} catch (_: Exception) {
@@ -424,8 +442,12 @@ class MainActivity : AppCompatActivity() {
timerJob?.cancel()
timerJob = lifecycleScope.launch {
delay(200)
- if ((prefs.appTheme == AppCompatDelegate.MODE_NIGHT_YES && getColorFromAttr(R.attr.primaryColor) != getColor(R.color.white))
- || (prefs.appTheme == AppCompatDelegate.MODE_NIGHT_NO && getColorFromAttr(R.attr.primaryColor) != getColor(R.color.black))
+ if ((prefs.appTheme == AppCompatDelegate.MODE_NIGHT_YES && getColorFromAttr(R.attr.primaryColor) != getColor(
+ R.color.white
+ ))
+ || (prefs.appTheme == AppCompatDelegate.MODE_NIGHT_NO && getColorFromAttr(R.attr.primaryColor) != getColor(
+ R.color.black
+ ))
)
restartLauncherOrCheckTheme(true)
}
@@ -446,20 +468,4 @@ class MainActivity : AppCompatActivity() {
}
super.onDestroy()
}
-
- @Deprecated("Deprecated in Java")
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- when (requestCode) {
- Constants.REQUEST_CODE_ENABLE_ADMIN -> {
- if (resultCode == Activity.RESULT_OK)
- prefs.lockModeOn = true
- }
-
- Constants.REQUEST_CODE_LAUNCHER_SELECTOR -> {
- if (resultCode == Activity.RESULT_OK)
- resetLauncherViaFakeActivity()
- }
- }
- }
-} \ No newline at end of file
+}
diff --git a/app/src/main/java/app/sidephonelauncher/data/Constants.kt b/app/src/main/java/app/sidephonelauncher/data/Constants.kt
index 02b0d90..dd812fc 100644
--- a/app/src/main/java/app/sidephonelauncher/data/Constants.kt
+++ b/app/src/main/java/app/sidephonelauncher/data/Constants.kt
@@ -47,6 +47,11 @@ object Constants {
const val NOTIFICATIONS = 2
}
+ object FocusIndicator {
+ const val PILL = 1
+ const val UNDERLINE = 2
+ }
+
object HomeAction {
const val OPEN_APP_DRAWER = "app.sidephonelauncher.action.OPEN_APP_DRAWER"
const val OPEN_PHONE = "app.sidephonelauncher.action.OPEN_PHONE"
@@ -103,7 +108,8 @@ object Constants {
const val URL_ABOUT_PAGE = "https://github.com/dirksierd/SidePhOnelauncher#readme"
const val URL_PRIVACY_POLICY = "https://github.com/dirksierd/SidePhOnelauncher#readme"
- const val URL_DOUBLE_TAP = "https://tanujnotes.notion.site/Double-tap-to-lock-Olauncher-0f7fb103ec1f47d7a90cdfdcd7fb86ef"
+ const val URL_DOUBLE_TAP =
+ "https://tanujnotes.notion.site/Double-tap-to-lock-Olauncher-0f7fb103ec1f47d7a90cdfdcd7fb86ef"
const val URL_GITHUB_REPO = "https://github.com/dirksierd/SidePhOnelauncher"
const val URL_APP_RELEASES = "https://github.com/dirksierd/SidePhOnelauncher/releases/latest"
const val URL_PRO_LAUNCHER = "https://play.google.com/store/apps/details?id=app.prolauncher"
@@ -123,4 +129,4 @@ object Constants {
const val DIGITAL_WELLBEING_SAMSUNG_PACKAGE_NAME = "com.samsung.android.forest"
const val DIGITAL_WELLBEING_SAMSUNG_ACTIVITY = "com.samsung.android.forest.launcher.LauncherActivity"
const val WALLPAPER_WORKER_NAME = "WALLPAPER_WORKER_NAME"
-} \ No newline at end of file
+}
diff --git a/app/src/main/java/app/sidephonelauncher/data/Prefs.kt b/app/src/main/java/app/sidephonelauncher/data/Prefs.kt
index 8252b05..2ef7a88 100644
--- a/app/src/main/java/app/sidephonelauncher/data/Prefs.kt
+++ b/app/src/main/java/app/sidephonelauncher/data/Prefs.kt
@@ -44,6 +44,7 @@ class Prefs(context: Context) {
private val SCREEN_TIME_LAST_UPDATED = "SCREEN_TIME_LAST_UPDATED"
private val LAUNCHER_RESTART_TIMESTAMP = "LAUNCHER_RECREATE_TIMESTAMP"
private val SHOWN_ON_DAY_OF_YEAR = "SHOWN_ON_DAY_OF_YEAR"
+ private val FOCUS_INDICATOR_STYLE = "FOCUS_INDICATOR_STYLE"
// Home button for recents feature disabled
// private val HOME_BUTTON_SHOW_RECENTS = "HOME_BUTTON_SHOW_RECENTS"
@@ -194,6 +195,10 @@ class Prefs(context: Context) {
get() = prefs.getBoolean(SWIPE_RIGHT_ENABLED, true)
set(value) = prefs.edit { putBoolean(SWIPE_RIGHT_ENABLED, value).apply() }
+ var focusIndicatorStyle: Int
+ get() = prefs.getInt(FOCUS_INDICATOR_STYLE, Constants.FocusIndicator.UNDERLINE)
+ set(value) = prefs.edit { putInt(FOCUS_INDICATOR_STYLE, value).apply() }
+
var appTheme: Int
get() = prefs.getInt(APP_THEME, AppCompatDelegate.MODE_NIGHT_YES)
set(value) = prefs.edit { putInt(APP_THEME, value).apply() }
@@ -680,4 +685,4 @@ class Prefs(context: Context) {
fun getAppRenameLabel(appPackage: String): String = prefs.getString(appPackage, "").toString()
fun setAppRenameLabel(appPackage: String, renameLabel: String) = prefs.edit { putString(appPackage, renameLabel) }
-} \ No newline at end of file
+}
diff --git a/app/src/main/java/app/sidephonelauncher/helper/Extensions.kt b/app/src/main/java/app/sidephonelauncher/helper/Extensions.kt
index a63f54e..4936068 100644
--- a/app/src/main/java/app/sidephonelauncher/helper/Extensions.kt
+++ b/app/src/main/java/app/sidephonelauncher/helper/Extensions.kt
@@ -4,6 +4,7 @@ import android.app.Activity
import android.app.AppOpsManager
import android.app.SearchManager
import android.app.role.RoleManager
+import android.hardware.display.DisplayManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -16,11 +17,10 @@ import android.os.UserHandle
import android.provider.Settings
import android.telephony.TelephonyManager
import android.view.View
-import android.view.WindowManager
+
import android.view.inputmethod.InputMethodManager
import androidx.annotation.RequiresApi
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
+
import app.sidephonelauncher.BuildConfig
import app.sidephonelauncher.R
import app.sidephonelauncher.data.Constants
@@ -29,7 +29,6 @@ import java.util.Locale
fun View.hideKeyboard() {
clearFocus()
- ViewCompat.getWindowInsetsController(this)?.hide(WindowInsetsCompat.Type.ime())
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
post { rootView?.requestApplyInsets() }
@@ -37,22 +36,24 @@ fun View.hideKeyboard() {
fun View.showKeyboard(show: Boolean = true) {
if (show.not()) return
- if (this.requestFocus())
+ val targetView = this
+ if (targetView.requestFocus())
postDelayed({
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
- imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
+ imm.showSoftInput(targetView, InputMethodManager.SHOW_IMPLICIT)
}, 100)
}
@RequiresApi(Build.VERSION_CODES.Q)
-fun Activity.showLauncherSelector(requestCode: Int) {
+fun Activity.createLauncherSelectorIntent(): Intent? {
val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
- if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)) {
- val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME)
- startActivityForResult(intent, requestCode)
- } else
+ return if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)) {
+ roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME)
+ } else {
resetDefaultLauncher()
+ null
+ }
}
fun Context.resetDefaultLauncher() {
@@ -95,8 +96,10 @@ fun Context.openSearch(query: String? = null) {
fun Context.isEinkDisplay(): Boolean {
return try {
- val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
- windowManager.defaultDisplay.refreshRate <= Constants.MIN_ANIM_REFRESH_RATE
+ val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ val refreshRate = displayManager.getDisplay(android.view.Display.DEFAULT_DISPLAY)?.refreshRate
+ ?: Float.MAX_VALUE
+ refreshRate <= Constants.MIN_ANIM_REFRESH_RATE
} catch (e: Exception) {
e.printStackTrace()
false
@@ -122,7 +125,10 @@ fun Context.searchOnPlayStore(query: String? = null): Boolean {
}
}
-fun Context.isPackageInstalled(packageName: String, userHandle: UserHandle = android.os.Process.myUserHandle()): Boolean {
+fun Context.isPackageInstalled(
+ packageName: String,
+ userHandle: UserHandle = android.os.Process.myUserHandle()
+): Boolean {
val launcher = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val activityInfo = launcher.getActivityList(packageName, userHandle)
return activityInfo.isNotEmpty()
diff --git a/app/src/main/java/app/sidephonelauncher/helper/Utils.kt b/app/src/main/java/app/sidephonelauncher/helper/Utils.kt
index 222977d..d66c3f8 100644
--- a/app/src/main/java/app/sidephonelauncher/helper/Utils.kt
+++ b/app/src/main/java/app/sidephonelauncher/helper/Utils.kt
@@ -16,7 +16,7 @@ import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
-import android.graphics.Point
+
import android.net.Uri
import android.os.Build
import android.os.UserHandle
@@ -30,6 +30,7 @@ import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.WindowManager
+import kotlin.math.roundToInt
import android.view.animation.LinearInterpolator
import android.widget.Toast
import androidx.annotation.AttrRes
@@ -408,10 +409,14 @@ suspend fun setWallpaper(appContext: Context, url: String): Boolean {
}
fun getScreenDimensions(context: Context): Pair<Int, Int> {
- val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- val point = Point()
- windowManager.defaultDisplay.getRealSize(point)
- return Pair(point.x, point.y)
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val bounds = windowManager.currentWindowMetrics.bounds
+ Pair(bounds.width(), bounds.height())
+ } else {
+ val metrics = context.resources.displayMetrics
+ Pair(metrics.widthPixels, metrics.heightPixels)
+ }
}
suspend fun getTodaysWallpaper(wallType: String, firstOpenTime: Long): String {
@@ -528,21 +533,20 @@ fun isAccessServiceEnabled(context: Context): Boolean {
0
}
if (enabled == 1) {
- val enabledServicesString: String? = Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
- return enabledServicesString?.contains(context.packageName + "/" + MyAccessibilityService::class.java.name) ?: false
+ val enabledServicesString: String? =
+ Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
+ return enabledServicesString?.contains(context.packageName + "/" + MyAccessibilityService::class.java.name)
+ ?: false
}
return false
}
fun isTablet(context: Context): Boolean {
- val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- val metrics = DisplayMetrics()
- windowManager.defaultDisplay.getMetrics(metrics)
+ val metrics = context.resources.displayMetrics
val widthInches = metrics.widthPixels / metrics.xdpi
val heightInches = metrics.heightPixels / metrics.ydpi
val diagonalInches = sqrt(widthInches.toDouble().pow(2.0) + heightInches.toDouble().pow(2.0))
- if (diagonalInches >= 7.0) return true
- return false
+ return diagonalInches >= 7.0
}
fun Context.isDarkThemeOn(): Boolean {
diff --git a/app/src/main/java/app/sidephonelauncher/ui/AppDrawerAdapter.kt b/app/src/main/java/app/sidephonelauncher/ui/AppDrawerAdapter.kt
index 10c5a41..c0186ba 100644
--- a/app/src/main/java/app/sidephonelauncher/ui/AppDrawerAdapter.kt
+++ b/app/src/main/java/app/sidephonelauncher/ui/AppDrawerAdapter.kt
@@ -2,6 +2,8 @@ package app.sidephonelauncher.ui
import android.content.Context
import android.content.pm.LauncherApps
+import android.content.res.ColorStateList
+import android.graphics.Paint
import android.os.UserHandle
import android.text.Editable
import android.text.TextWatcher
@@ -22,6 +24,7 @@ import app.sidephonelauncher.data.Constants
import app.sidephonelauncher.databinding.AdapterAppDrawerBinding
import app.sidephonelauncher.databinding.AdapterAppDrawerSpacerBinding
import app.sidephonelauncher.databinding.AdapterPrivateSpaceHeaderBinding
+import app.sidephonelauncher.helper.getColorFromAttr
import app.sidephonelauncher.helper.hideKeyboard
import app.sidephonelauncher.helper.isSystemApp
import app.sidephonelauncher.helper.showKeyboard
@@ -30,6 +33,7 @@ import java.text.Normalizer
class AppDrawerAdapter(
private var flag: Int,
private val appLabelGravity: Int,
+ private val focusIndicatorStyle: Int,
private val appClickListener: (AppModel) -> Unit,
private val appInfoListener: (AppModel) -> Unit,
private val appDeleteListener: (AppModel) -> Unit,
@@ -128,6 +132,7 @@ class AppDrawerAdapter(
is ViewHolder -> holder.bind(
flag,
appLabelGravity,
+ focusIndicatorStyle,
myUserHandle,
appModel,
appClickListener,
@@ -260,6 +265,7 @@ class AppDrawerAdapter(
fun bind(
flag: Int,
appLabelGravity: Int,
+ focusIndicatorStyle: Int,
myUserHandle: UserHandle,
appModel: AppModel,
clickListener: (AppModel) -> Unit,
@@ -283,12 +289,14 @@ class AppDrawerAdapter(
appTitle.gravity = appLabelGravity
otherProfileIndicator.isVisible = appModel.user != myUserHandle && !isSpecialActionItem
+ appRow.setBackgroundResource(
+ if (focusIndicatorStyle == Constants.FocusIndicator.PILL) R.drawable.bg_focus_inverted_selector
+ else android.R.color.transparent
+ )
appRow.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
- appTitle.isSelected = hasFocus
- otherProfileIndicator.isSelected = hasFocus
+ updateFocusAppearance(root.context, appTitle, otherProfileIndicator, hasFocus, focusIndicatorStyle)
}
- appTitle.isSelected = appRow.isFocused
- otherProfileIndicator.isSelected = appRow.isFocused
+ updateFocusAppearance(root.context, appTitle, otherProfileIndicator, appRow.isFocused, focusIndicatorStyle)
appRow.setOnClickListener { clickListener(appModel) }
appRow.setOnLongClickListener {
@@ -424,6 +432,36 @@ class AppDrawerAdapter(
appHide.setOnClickListener { appHideListener(appModel, bindingAdapterPosition) }
}
+ private fun updateFocusAppearance(
+ context: Context,
+ textView: android.widget.TextView,
+ indicatorView: android.widget.ImageView,
+ active: Boolean,
+ focusIndicatorStyle: Int,
+ ) {
+ if (focusIndicatorStyle == Constants.FocusIndicator.PILL) {
+ textView.setTextColor(
+ if (active) context.getColor(R.color.black)
+ else context.getColorFromAttr(R.attr.primaryColor)
+ )
+ textView.paintFlags = textView.paintFlags and Paint.UNDERLINE_TEXT_FLAG.inv()
+ indicatorView.imageTintList = ColorStateList.valueOf(
+ if (active) context.getColor(R.color.black)
+ else context.getColorFromAttr(R.attr.primaryColor)
+ )
+ } else {
+ textView.setTextColor(context.getColorFromAttr(R.attr.primaryColor))
+ textView.paintFlags = if (active) {
+ textView.paintFlags or Paint.UNDERLINE_TEXT_FLAG
+ } else {
+ textView.paintFlags and Paint.UNDERLINE_TEXT_FLAG.inv()
+ }
+ indicatorView.imageTintList = ColorStateList.valueOf(
+ context.getColorFromAttr(R.attr.primaryColor)
+ )
+ }
+ }
+
private fun getAppName(context: Context, appPackage: String, user: UserHandle): String {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
return try {
diff --git a/app/src/main/java/app/sidephonelauncher/ui/AppDrawerFragment.kt b/app/src/main/java/app/sidephonelauncher/ui/AppDrawerFragment.kt
index fec7eeb..a831807 100644
--- a/app/src/main/java/app/sidephonelauncher/ui/AppDrawerFragment.kt
+++ b/app/src/main/java/app/sidephonelauncher/ui/AppDrawerFragment.kt
@@ -104,6 +104,7 @@ class AppDrawerFragment : Fragment() {
if (event.action == KeyEvent.ACTION_UP) focusSearchInput()
true
}
+
else -> {
if (event.action != KeyEvent.ACTION_DOWN) return@setOnKeyListener false
when (keyCode) {
@@ -118,10 +119,12 @@ class AppDrawerFragment : Fragment() {
}
false
}
+
KeyEvent.KEYCODE_DEL -> {
focusSearchInput()
true
}
+
else -> false
}
}
@@ -134,6 +137,7 @@ class AppDrawerFragment : Fragment() {
if (event.action == KeyEvent.ACTION_UP) findNavController().popBackStack()
true
}
+
else -> {
if (event.action != KeyEvent.ACTION_DOWN) return@setOnKeyListener false
when (keyCode) {
@@ -141,6 +145,7 @@ class AppDrawerFragment : Fragment() {
focusFirstLaunchableItem()
true
}
+
KeyEvent.KEYCODE_DPAD_LEFT -> handleSearchLeftKey()
else -> false
}
@@ -154,6 +159,7 @@ class AppDrawerFragment : Fragment() {
if (event.action == KeyEvent.ACTION_UP) findNavController().popBackStack()
true
}
+
else -> {
if (event.action != KeyEvent.ACTION_DOWN) return@setOnKeyListener false
when (keyCode) {
@@ -161,6 +167,7 @@ class AppDrawerFragment : Fragment() {
focusFirstLaunchableItem()
true
}
+
KeyEvent.KEYCODE_DPAD_LEFT -> handleSearchLeftKey()
else -> false
}
@@ -251,10 +258,13 @@ class AppDrawerFragment : Fragment() {
val result = try {
val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val subtype = imm.currentInputMethodSubtype
+
+ @Suppress("DEPRECATION")
+ val legacyLocale = subtype?.locale.orEmpty()
val language = when {
subtype == null -> ""
subtype.languageTag.isNotEmpty() -> subtype.languageTag
- else -> subtype.locale
+ else -> legacyLocale
}
language.startsWith("zh") || language.startsWith("ja") || language.startsWith("ko")
} catch (e: Exception) {
@@ -268,6 +278,7 @@ class AppDrawerFragment : Fragment() {
adapter = AppDrawerAdapter(
flag,
prefs.appLabelAlignment,
+ prefs.focusIndicatorStyle,
appClickListener = { appModel ->
viewModel.selectedApp(appModel, flag)
if (flag == Constants.FLAG_LAUNCH_APP || flag == Constants.FLAG_HIDDEN_APPS)
@@ -503,6 +514,7 @@ class AppDrawerFragment : Fragment() {
if (onTop)
binding.search.hideKeyboard()
}
+
RecyclerView.SCROLL_STATE_IDLE -> {
if (!recyclerView.canScrollVertically(1))
binding.search.hideKeyboard()
diff --git a/app/src/main/java/app/sidephonelauncher/ui/HomeAppTextView.kt b/app/src/main/java/app/sidephonelauncher/ui/HomeAppTextView.kt
index 65775e2..39757f7 100644
--- a/app/src/main/java/app/sidephonelauncher/ui/HomeAppTextView.kt
+++ b/app/src/main/java/app/sidephonelauncher/ui/HomeAppTextView.kt
@@ -7,6 +7,8 @@ import android.graphics.Paint
import android.util.AttributeSet
import android.widget.TextView
import app.sidephonelauncher.R
+import app.sidephonelauncher.data.Constants
+import app.sidephonelauncher.data.Prefs
import app.sidephonelauncher.helper.dpToPx
import app.sidephonelauncher.helper.getColorFromAttr
@@ -16,11 +18,9 @@ class HomeAppTextView @JvmOverloads constructor(
defStyleAttr: Int = android.R.attr.textViewStyle,
) : TextView(context, attrs, defStyleAttr) {
- private val activeTextColor = Color.BLACK
+ private val prefs = Prefs(context)
private val inactiveTextColor = context.getColorFromAttr(R.attr.primaryColor)
- private val activeHintColor = Color.BLACK
private val inactiveHintColor = context.getColorFromAttr(R.attr.primaryColorTrans80)
-
private val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
alpha = 220
@@ -39,6 +39,9 @@ class HomeAppTextView @JvmOverloads constructor(
paddingEnd + reservedSideSpacePx,
paddingBottom,
)
+ setBackgroundResource(
+ if (isPillStyle()) R.drawable.bg_focus_inverted_selector else android.R.color.transparent
+ )
applyFocusAppearance()
}
@@ -54,9 +57,24 @@ class HomeAppTextView @JvmOverloads constructor(
if (showNotificationDot) invalidate()
}
+ private fun isPillStyle(): Boolean {
+ return prefs.focusIndicatorStyle == Constants.FocusIndicator.PILL
+ }
+
private fun applyFocusAppearance(active: Boolean = hasFocus() || isPressed) {
- super.setTextColor(if (active) activeTextColor else inactiveTextColor)
- setHintTextColor(if (active) activeHintColor else inactiveHintColor)
+ if (isPillStyle()) {
+ super.setTextColor(if (active) context.getColor(R.color.black) else inactiveTextColor)
+ setHintTextColor(if (active) context.getColor(R.color.black) else inactiveHintColor)
+ paintFlags = paintFlags and Paint.UNDERLINE_TEXT_FLAG.inv()
+ } else {
+ super.setTextColor(inactiveTextColor)
+ setHintTextColor(inactiveHintColor)
+ paintFlags = if (active) {
+ paintFlags or Paint.UNDERLINE_TEXT_FLAG
+ } else {
+ paintFlags and Paint.UNDERLINE_TEXT_FLAG.inv()
+ }
+ }
setShadowLayer(0f, 0f, 0f, Color.TRANSPARENT)
}
@@ -66,7 +84,7 @@ class HomeAppTextView @JvmOverloads constructor(
super.onDraw(canvas)
if (!showNotificationDot) return
- dotPaint.color = if (active) Color.BLACK else Color.WHITE
+ dotPaint.color = if (isPillStyle() && active) Color.BLACK else Color.WHITE
val cx = width - reservedSideSpacePx / 2f
val cy = height / 2f
canvas.drawCircle(cx, cy, dotRadiusPx, dotPaint)
diff --git a/app/src/main/java/app/sidephonelauncher/ui/HomeFragment.kt b/app/src/main/java/app/sidephonelauncher/ui/HomeFragment.kt
index 9ef1e27..c687e63 100644
--- a/app/src/main/java/app/sidephonelauncher/ui/HomeFragment.kt
+++ b/app/src/main/java/app/sidephonelauncher/ui/HomeFragment.kt
@@ -5,6 +5,8 @@ import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.Paint
import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
@@ -37,6 +39,7 @@ import app.sidephonelauncher.helper.appUsagePermissionGranted
import app.sidephonelauncher.helper.dpToPx
import app.sidephonelauncher.helper.expandNotificationDrawer
import app.sidephonelauncher.helper.getChangedAppTheme
+import app.sidephonelauncher.helper.getColorFromAttr
import app.sidephonelauncher.helper.getUserHandleFromString
import app.sidephonelauncher.helper.isPackageInstalled
import app.sidephonelauncher.helper.openAlarmApp
@@ -389,10 +392,33 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
}
private fun rememberHomeFocus(view: View, hasFocus: Boolean) {
+ if (view is TextView && view !is HomeAppTextView) {
+ updateHeaderFocusAppearance(view, hasFocus)
+ }
if (!hasFocus) return
viewModel.setLastHomeFocusedView(view.id)
}
+ private fun updateHeaderFocusAppearance(textView: TextView, active: Boolean) {
+ if (prefs.focusIndicatorStyle == Constants.FocusIndicator.PILL) {
+ textView.setBackgroundResource(R.drawable.bg_focus_inverted_selector)
+ textView.setTextColor(
+ if (active) requireContext().getColor(R.color.black)
+ else requireContext().getColorFromAttr(R.attr.primaryColor)
+ )
+ textView.paintFlags = textView.paintFlags and Paint.UNDERLINE_TEXT_FLAG.inv()
+ } else {
+ textView.setBackgroundResource(android.R.color.transparent)
+ textView.setTextColor(requireContext().getColorFromAttr(R.attr.primaryColor))
+ textView.paintFlags = if (active) {
+ textView.paintFlags or Paint.UNDERLINE_TEXT_FLAG
+ } else {
+ textView.paintFlags and Paint.UNDERLINE_TEXT_FLAG.inv()
+ }
+ }
+ textView.setShadowLayer(0f, 0f, 0f, Color.TRANSPARENT)
+ }
+
private fun redirectRootFocusToHomeTargetSafely() {
ensureHomeFocus()
}
@@ -904,6 +930,9 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
it.isFocusable = true
it.isFocusableInTouchMode = true
}
+ listOf(binding.clock as TextView, binding.date).forEach {
+ updateHeaderFocusAppearance(it, it.hasFocus())
+ }
}
private fun hideHomeApps() {
diff --git a/app/src/main/java/app/sidephonelauncher/ui/SettingsFragment.kt b/app/src/main/java/app/sidephonelauncher/ui/SettingsFragment.kt
index b4adaea..5bece50 100644
--- a/app/src/main/java/app/sidephonelauncher/ui/SettingsFragment.kt
+++ b/app/src/main/java/app/sidephonelauncher/ui/SettingsFragment.kt
@@ -14,6 +14,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationManagerCompat
import androidx.core.os.bundleOf
@@ -55,6 +56,12 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
private val binding get() = _binding!!
private val showPentastic = System.currentTimeMillis() % 2 == 0L
private var showInstagram = false
+ private val adminPermissionLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == android.app.Activity.RESULT_OK) {
+ prefs.lockModeOn = true
+ populateLockSettings()
+ }
+ }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentSettingsBinding.inflate(inflater, container, false)
@@ -87,6 +94,7 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
populateAlignment()
populateStatusBar()
populateNotificationDots()
+ populateFocusIndicatorStyle()
populateDateTime()
populateSwipeApps()
populateSwipeDownAction()
@@ -109,6 +117,7 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
binding.appsNumSelectLayout.visibility = View.GONE
binding.dateTimeSelectLayout.visibility = View.GONE
binding.appThemeSelectLayout.visibility = View.GONE
+ binding.focusIndicatorStyleSelectLayout?.visibility = View.GONE
binding.swipeDownSelectLayout.visibility = View.GONE
if (view.id != R.id.textSizeMinus && view.id != R.id.textSizePlus) {
if (binding.textSizesLayout.isVisible) {
@@ -139,6 +148,9 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
R.id.alignmentBottom -> updateHomeBottomAlignment()
R.id.statusBar -> toggleStatusBar()
R.id.notificationDotsRow -> viewModel.showDialog.postValue(Constants.Dialog.NOTIFICATION_DOTS)
+ R.id.focusIndicatorStyle -> binding.focusIndicatorStyleSelectLayout?.visibility = View.VISIBLE
+ R.id.focusIndicatorPill -> updateFocusIndicatorStyle(Constants.FocusIndicator.PILL)
+ R.id.focusIndicatorUnderline -> updateFocusIndicatorStyle(Constants.FocusIndicator.UNDERLINE)
R.id.dateTime -> binding.dateTimeSelectLayout.visibility = View.VISIBLE
R.id.dateTimeOn -> toggleDateTime(Constants.DateTime.ON)
R.id.dateTimeOff -> toggleDateTime(Constants.DateTime.OFF)
@@ -187,6 +199,7 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
R.id.twitter -> requireContext().openUrl(
if (showInstagram) Constants.URL_INSTA_TANUJ else Constants.URL_TWITTER_TANUJ
)
+
R.id.github -> requireContext().openUrl(Constants.URL_GITHUB_REPO)
R.id.privacy -> requireContext().openUrl(Constants.URL_PRIVACY_POLICY)
R.id.footer -> {
@@ -240,6 +253,9 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
binding.alignmentBottom.setOnClickListener(this)
binding.statusBar.setOnClickListener(this)
binding.notificationDotsRow.setOnClickListener(this)
+ binding.focusIndicatorStyle?.setOnClickListener(this)
+ binding.focusIndicatorPill?.setOnClickListener(this)
+ binding.focusIndicatorUnderline?.setOnClickListener(this)
binding.dateTime.setOnClickListener(this)
binding.dateTimeOn.setOnClickListener(this)
binding.dateTimeOff.setOnClickListener(this)
@@ -349,6 +365,21 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
)
}
+ private fun populateFocusIndicatorStyle() {
+ binding.focusIndicatorStyle?.text = getString(
+ when (prefs.focusIndicatorStyle) {
+ Constants.FocusIndicator.PILL -> R.string.pill
+ else -> R.string.underline
+ }
+ )
+ }
+
+ private fun updateFocusIndicatorStyle(style: Int) {
+ prefs.focusIndicatorStyle = style
+ populateFocusIndicatorStyle()
+ requireActivity().recreate()
+ }
+
private fun toggleDateTime(selected: Int) {
prefs.dateTimeVisibility = selected
populateDateTime()
@@ -440,7 +471,7 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
getString(R.string.admin_permission_message)
)
- requireActivity().startActivityForResult(intent, Constants.REQUEST_CODE_ENABLE_ADMIN)
+ adminPermissionLauncher.launch(intent)
}
}
populateLockSettings()
@@ -591,7 +622,10 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
private fun updateHomeBottomAlignment() {
if (viewModel.isSidePhOnelauncherDefault.value != true) {
- requireContext().showToast(getString(R.string.please_set_sidephonelauncher_as_default_first), Toast.LENGTH_LONG)
+ requireContext().showToast(
+ getString(R.string.please_set_sidephonelauncher_as_default_first),
+ Toast.LENGTH_LONG
+ )
return
}
prefs.homeBottomAlignment = !prefs.homeBottomAlignment
@@ -709,4 +743,4 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
viewModel.checkForMessages.call()
super.onDestroy()
}
-} \ No newline at end of file
+}
diff --git a/app/src/main/res/layout/adapter_app_drawer.xml b/app/src/main/res/layout/adapter_app_drawer.xml
index 1fc11fd..283ea35 100644
--- a/app/src/main/res/layout/adapter_app_drawer.xml
+++ b/app/src/main/res/layout/adapter_app_drawer.xml
@@ -11,7 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
- android:background="@drawable/bg_focus_inverted_selector"
+ android:background="@android:color/transparent"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
android:focusableInTouchMode="false"
@@ -25,28 +25,25 @@
style="@style/TextLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:duplicateParentState="true"
android:layout_weight="1"
android:maxLines="1"
android:shadowColor="@android:color/transparent"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="0"
- android:text="@string/app"
- android:textColor="@color/text_focus_inverted" />
+ android:text="@string/app" />
</LinearLayout>
<ImageView
android:id="@+id/otherProfileIndicator"
android:layout_width="5dp"
android:layout_height="5dp"
- android:duplicateParentState="true"
android:layout_gravity="center_vertical"
android:layout_marginStart="22dp"
android:layout_marginTop="1dp"
android:gravity="center"
android:src="@drawable/ic_rounded"
- app:tint="@color/icon_tint_focus_inverted" />
+ app:tint="?attr/primaryColor" />
<LinearLayout
android:id="@+id/appHideLayout"
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 12ff6f9..7302854 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -616,6 +616,74 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:paddingVertical="8dp"
+ android:text="@string/focus_indicator"
+ android:textColor="?attr/primaryColor" />
+
+ <TextView
+ android:id="@+id/focusIndicatorStyle"
+ style="@style/TextSmallBold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|bottom"
+ android:layout_marginEnd="4dp"
+ android:padding="8dp"
+ android:text="@string/underline" />
+
+ <LinearLayout
+ android:id="@+id/focusIndicatorStyleSelectLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true"
+ android:background="@drawable/rounded_primary_gradient"
+ android:elevation="8dp"
+ android:orientation="horizontal"
+ android:outlineAmbientShadowColor="@color/colorPrimaryDark"
+ android:outlineSpotShadowColor="@color/colorPrimaryDark"
+ android:paddingHorizontal="12dp"
+ android:paddingVertical="2dp"
+ android:visibility="gone"
+ tools:ignore="UnusedAttribute">
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <TextView
+ android:id="@+id/focusIndicatorPill"
+ style="@style/TextSmallBold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="24dp"
+ android:gravity="center"
+ android:paddingVertical="6dp"
+ android:text="@string/pill" />
+
+ <TextView
+ android:id="@+id/focusIndicatorUnderline"
+ style="@style/TextSmallBold"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingVertical="6dp"
+ android:text="@string/underline" />
+
+ </LinearLayout>
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:animateLayoutChanges="true">
+
+ <TextView
+ style="@style/TextSmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:paddingVertical="8dp"
android:text="@string/theme_mode"
android:textColor="?attr/primaryColor" />
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index a4d0e42..7e5d936 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -76,22 +76,12 @@
</style>
<style name="TextDefaultFocusableInverted" parent="TextDefault">
- <item name="android:background">@drawable/bg_focus_inverted_selector</item>
- <item name="android:textColor">@color/text_focus_inverted</item>
- <item name="android:shadowColor">@android:color/transparent</item>
- <item name="android:shadowDx">0</item>
- <item name="android:shadowDy">0</item>
- <item name="android:shadowRadius">0</item>
+ <item name="android:background">@android:color/transparent</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
<style name="TextLargeFocusableInverted" parent="TextLarge">
- <item name="android:background">@drawable/bg_focus_inverted_selector</item>
- <item name="android:textColor">@color/text_focus_inverted</item>
- <item name="android:shadowColor">@android:color/transparent</item>
- <item name="android:shadowDx">0</item>
- <item name="android:shadowDy">0</item>
- <item name="android:shadowRadius">0</item>
+ <item name="android:background">@android:color/transparent</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 461cb09..bed629e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -73,6 +73,9 @@
<string name="rename">Rename</string>
<string name="show_date_time">Show date time</string>
<string name="appearance">Appearance</string>
+ <string name="focus_indicator">Focus indicator</string>
+ <string name="pill">Pill</string>
+ <string name="underline">Underline</string>
<string name="gestures">Gestures</string>
<string name="theme_mode">Theme mode</string>
<string name="system_default">System</string>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 09d42fb..b1eba4d 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -76,22 +76,12 @@
</style>
<style name="TextDefaultFocusableInverted" parent="TextDefault">
- <item name="android:background">@drawable/bg_focus_inverted_selector</item>
- <item name="android:textColor">@color/text_focus_inverted</item>
- <item name="android:shadowColor">@android:color/transparent</item>
- <item name="android:shadowDx">0</item>
- <item name="android:shadowDy">0</item>
- <item name="android:shadowRadius">0</item>
+ <item name="android:background">@android:color/transparent</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
<style name="TextLargeFocusableInverted" parent="TextLarge">
- <item name="android:background">@drawable/bg_focus_inverted_selector</item>
- <item name="android:textColor">@color/text_focus_inverted</item>
- <item name="android:shadowColor">@android:color/transparent</item>
- <item name="android:shadowDx">0</item>
- <item name="android:shadowDy">0</item>
- <item name="android:shadowRadius">0</item>
+ <item name="android:background">@android:color/transparent</item>
<item name="android:defaultFocusHighlightEnabled">false</item>
</style>
diff --git a/gradle.properties b/gradle.properties
index 1fcc4d4..ae3a1e8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,6 +20,5 @@ android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.suppressUnsupportedCompileSdk=34
-android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false \ No newline at end of file