diff options
| author | Dirk Sierd de Vries <contact@lecapuchon.nl> | 2026-08-12 15:39:34 +0200 |
|---|---|---|
| committer | Dirk Sierd de Vries <contact@lecapuchon.nl> | 2026-08-12 15:39:34 +0200 |
| commit | 1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad (patch) | |
| tree | d6bd764f0a5a53533d8d90877a368c0c42c3cea2 | |
| parent | 2dcff5bc664a00b61283a95ebfdfb5717f626d94 (diff) | |
| download | SidePhOnelauncher-1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad.tar.gz SidePhOnelauncher-1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad.tar.bz2 SidePhOnelauncher-1d1a57ae60c973439bd3ad0bd11bb72f3331c3ad.zip | |
Add configurable focus indicator styles
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 |
