Removed app menu closing restriction, made about page image open app info and organized code into smaller packages

This commit is contained in:
ottoptj 2024-08-12 10:31:34 +03:00
commit 4cb52fe719
26 changed files with 108 additions and 50 deletions

View file

@ -25,7 +25,7 @@
android:theme="@style/Theme.YamLauncher" android:theme="@style/Theme.YamLauncher"
tools:targetApi="34"> tools:targetApi="34">
<activity <activity
android:name=".SettingsActivity" android:name=".settings.SettingsActivity"
android:theme="@style/SettingsTheme" android:theme="@style/SettingsTheme"
android:exported="false" /> android:exported="false" />
<activity <activity
@ -46,7 +46,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".ScreenLockService" android:name=".tasks.ScreenLockService"
android:exported="false" android:exported="false"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter> <intent-filter>

View file

@ -18,6 +18,8 @@ import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.AppCompatButton
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import eu.ottop.yamlauncher.databinding.ActivityMainBinding import eu.ottop.yamlauncher.databinding.ActivityMainBinding
import eu.ottop.yamlauncher.settings.SharedPreferenceManager
import eu.ottop.yamlauncher.utils.Animations
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AppActionMenu { class AppActionMenu {
@ -33,7 +35,7 @@ class AppActionMenu {
userHandle: UserHandle, userHandle: UserHandle,
workProfile: Int, workProfile: Int,
launcherApps: LauncherApps, launcherApps: LauncherApps,
mainActivity: LauncherActivityInfo? appActivity: LauncherActivityInfo?
){ ){
val animations = Animations(activity) val animations = Animations(activity)
val sharedPreferenceManager = SharedPreferenceManager(activity) val sharedPreferenceManager = SharedPreferenceManager(activity)
@ -41,9 +43,9 @@ class AppActionMenu {
actionMenu.findViewById<TextView>(R.id.info).setOnClickListener { actionMenu.findViewById<TextView>(R.id.info).setOnClickListener {
// Launch app info in phone settings // Launch app info in phone settings
if (mainActivity != null) { if (appActivity != null) {
launcherApps.startAppDetailsActivity( launcherApps.startAppDetailsActivity(
mainActivity.componentName, appActivity.componentName,
userHandle, userHandle,
null, null,
null null
@ -72,7 +74,7 @@ class AppActionMenu {
val editText = editLayout.findViewById<EditText>(R.id.appNameEdit) val editText = editLayout.findViewById<EditText>(R.id.appNameEdit)
val resetButton = editLayout.findViewById<AppCompatButton>(R.id.reset) val resetButton = editLayout.findViewById<AppCompatButton>(R.id.reset)
val app = Triple(mainActivity!!, userHandle, workProfile) val app = Triple(appActivity!!, userHandle, workProfile)
searchView.visibility = View.INVISIBLE searchView.visibility = View.INVISIBLE
editText.requestFocus() editText.requestFocus()

View file

@ -17,6 +17,9 @@ import android.widget.TextView
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import eu.ottop.yamlauncher.settings.SharedPreferenceManager
import eu.ottop.yamlauncher.utils.AppUtils
import eu.ottop.yamlauncher.utils.UIUtils
class AppMenuAdapter( class AppMenuAdapter(

View file

@ -33,6 +33,18 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import eu.ottop.yamlauncher.databinding.ActivityMainBinding import eu.ottop.yamlauncher.databinding.ActivityMainBinding
import eu.ottop.yamlauncher.settings.SettingsActivity
import eu.ottop.yamlauncher.settings.SharedPreferenceManager
import eu.ottop.yamlauncher.tasks.BatteryReceiver
import eu.ottop.yamlauncher.tasks.ScreenLockService
import eu.ottop.yamlauncher.utils.Animations
import eu.ottop.yamlauncher.utils.AppMenuEdgeFactory
import eu.ottop.yamlauncher.utils.AppMenuLinearLayoutManager
import eu.ottop.yamlauncher.utils.AppUtils
import eu.ottop.yamlauncher.utils.GestureUtils
import eu.ottop.yamlauncher.utils.StringUtils
import eu.ottop.yamlauncher.utils.UIUtils
import eu.ottop.yamlauncher.utils.WeatherSystem
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -74,8 +86,7 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
private var isBatteryReceiverRegistered = false private var isBatteryReceiverRegistered = false
private var isJobActive = true var isJobActive = true
var canExit = true
private val swipeThreshold = 100 private val swipeThreshold = 100
private val swipeVelocityThreshold = 100 private val swipeVelocityThreshold = 100
@ -525,14 +536,10 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
binding.appView.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom -> binding.appView.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
if (bottom - top > oldBottom - oldTop) { if (bottom - top > oldBottom - oldTop) {
// Allow the app menu to be closed after the keyboard is closed // If keyboard is closed, remove cursor from the search bar
canExit = true
searchView.clearFocus() searchView.clearFocus()
} }
else if (bottom - top < oldBottom - oldTop) {
// The app menu can't be closed with the keyboard open
canExit = false
}
} }
searchView.addTextChangedListener(object : searchView.addTextChangedListener(object :
@ -666,7 +673,7 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
) { ) {
textView.visibility = View.INVISIBLE textView.visibility = View.INVISIBLE
animations.fadeViewIn(actionMenuLayout) animations.fadeViewIn(actionMenuLayout)
val mainActivity = val appActivity =
launcherApps.getActivityList(appInfo.applicationInfo.packageName, userHandle) launcherApps.getActivityList(appInfo.applicationInfo.packageName, userHandle)
.firstOrNull() .firstOrNull()
appActionMenu.setActionListeners( appActionMenu.setActionListeners(
@ -680,7 +687,7 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
userHandle, userHandle,
userProfile, userProfile,
launcherApps, launcherApps,
mainActivity appActivity
) )
} }

View file

@ -0,0 +1,44 @@
package eu.ottop.yamlauncher.settings
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.StringUtils
class AboutFragment : Fragment() {
private val stringUtils = StringUtils()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_about, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set up about page links
stringUtils.setLink(requireActivity().findViewById(R.id.creditText), getString(R.string.my_website_link))
stringUtils.setLink(requireActivity().findViewById(R.id.codebergLink), getString(R.string.codeberg_link))
stringUtils.setLink(requireActivity().findViewById(R.id.githubLink), getString(R.string.github_link))
stringUtils.setLink(requireActivity().findViewById(R.id.stripeLink), getString(R.string.stripe_link))
stringUtils.setLink(requireActivity().findViewById(R.id.liberaLink), getString(R.string.libera_link))
stringUtils.setLink(requireActivity().findViewById(R.id.weatherLink), getString(R.string.weather_link))
requireActivity().findViewById<ImageView>(R.id.iconView).setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${requireContext().packageName}")
}
startActivity(intent)
}
}
}

View file

@ -0,0 +1,88 @@
package eu.ottop.yamlauncher.settings
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.os.UserHandle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.UIUtils
class GestureAppsAdapter(
private val context: Context,
var apps: MutableList<Triple<LauncherActivityInfo, UserHandle, Int>>,
private val itemClickListener: OnItemClickListener
) :
RecyclerView.Adapter<GestureAppsAdapter.AppViewHolder>() {
private val sharedPreferenceManager = SharedPreferenceManager(context)
private val uiUtils = UIUtils(context)
interface OnItemClickListener {
fun onItemClick(appInfo: LauncherActivityInfo, profile: Int)
}
inner class AppViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val listItem: FrameLayout = itemView.findViewById(R.id.listItem)
val textView: TextView = listItem.findViewById(R.id.appName)
init {
textView.setOnClickListener {
val position = bindingAdapterPosition
val app = apps[position].first
itemClickListener.onItemClick(app, apps[position].third)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.app_item_layout, parent, false)
return AppViewHolder(view)
}
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
val app = apps[position]
if (app.third != 0) {
holder.textView.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(context.resources,
R.drawable.ic_work_app, null),null,null,null)
}
else {
holder.textView.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(context.resources,
R.drawable.ic_empty, null),null,null,null)
}
uiUtils.setAppAlignment(holder.textView)
uiUtils.setAppSize(holder.textView)
// Does not need to be specially updated since it's in a separate activity and thus reloads when opened again
val appInfo = app.first.activityInfo.applicationInfo
holder.textView.text = sharedPreferenceManager.getAppName(
app.first.applicationInfo.packageName,
app.third,
context.packageManager.getApplicationLabel(appInfo)
)
holder.textView.visibility = View.VISIBLE
}
override fun getItemCount(): Int {
return apps.size
}
@SuppressLint("NotifyDataSetChanged")
fun updateApps(newApps: List<Triple<LauncherActivityInfo, UserHandle, Int>>) {
apps = newApps.toMutableList()
notifyDataSetChanged()
}
}

View file

@ -0,0 +1,169 @@
package eu.ottop.yamlauncher.settings
import android.app.AlertDialog
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.os.Bundle
import android.os.UserHandle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import eu.ottop.yamlauncher.utils.AppMenuEdgeFactory
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.AppUtils
import eu.ottop.yamlauncher.utils.StringUtils
import eu.ottop.yamlauncher.utils.UIUtils
import kotlinx.coroutines.launch
class GestureAppsFragment(private val direction: String) : Fragment(),
GestureAppsAdapter.OnItemClickListener {
private var adapter: GestureAppsAdapter? = null
private lateinit var sharedPreferenceManager: SharedPreferenceManager
private var stringUtils = StringUtils()
private lateinit var appUtils: AppUtils
private lateinit var launcherApps: LauncherApps
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_gesture_apps, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
launcherApps = requireContext().getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
appUtils = AppUtils(requireContext(), launcherApps)
sharedPreferenceManager = SharedPreferenceManager(requireContext())
lifecycleScope.launch {
adapter = GestureAppsAdapter(
requireContext(),
appUtils.getInstalledApps().toMutableList(),
this@GestureAppsFragment
)
val recyclerView = view.findViewById<RecyclerView>(R.id.gestureAppRecycler)
val appMenuEdgeFactory = AppMenuEdgeFactory(requireActivity())
val uiUtils = UIUtils(requireContext())
recyclerView.edgeEffectFactory = appMenuEdgeFactory
recyclerView.adapter = adapter
recyclerView.scrollToPosition(0)
val searchView = view.findViewById<TextInputEditText>(R.id.gestureAppSearch)
uiUtils.setMenuTitleAlignment(view.findViewById(R.id.gestureMenuTitle))
uiUtils.setSearchAlignment(searchView)
uiUtils.setSearchSize(searchView)
recyclerView.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
if (bottom - top > oldBottom - oldTop) {
searchView.clearFocus()
}
}
searchView.addTextChangedListener(object :
TextWatcher {
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
lifecycleScope.launch {
filterItems(s.toString())
}
}
})
}
}
private suspend fun filterItems(query: String?) {
val cleanQuery = stringUtils.cleanString(query)
val newFilteredApps = mutableListOf<Triple<LauncherActivityInfo, UserHandle, Int>>()
val updatedApps = appUtils.getInstalledApps()
getFilteredApps(cleanQuery, newFilteredApps, updatedApps)
applySearch(newFilteredApps)
}
private fun getFilteredApps(cleanQuery: String?, newFilteredApps: MutableList<Triple<LauncherActivityInfo, UserHandle, Int>>, updatedApps: List<Triple<LauncherActivityInfo, UserHandle, Int>>) {
if (cleanQuery.isNullOrEmpty()) {
newFilteredApps.addAll(updatedApps)
} else {
updatedApps.forEach {
val cleanItemText = stringUtils.cleanString(sharedPreferenceManager.getAppName(
it.first.applicationInfo.packageName,
it.third,
requireContext().packageManager.getApplicationLabel(it.first.applicationInfo)
).toString())
if (cleanItemText != null) {
if (cleanItemText.contains(cleanQuery, ignoreCase = true)) {
newFilteredApps.add(it)
}
}
}
}
}
private fun applySearch(newFilteredApps: MutableList<Triple<LauncherActivityInfo, UserHandle, Int>>) {
adapter?.updateApps(newFilteredApps)
}
private fun showConfirmationDialog(appInfo: LauncherActivityInfo, appName: String, profile: Int) {
AlertDialog.Builder(requireContext()).apply {
setTitle("Confirmation")
setMessage("Are you sure you want to choose $appName?")
setPositiveButton("Yes") { _, _ ->
performConfirmedAction(appInfo, appName, profile)
}
setNegativeButton("Cancel") { _, _ ->
}
}.create().show()
}
private fun performConfirmedAction(appInfo: LauncherActivityInfo, appName: String, profile: Int) {
sharedPreferenceManager.setGestures(
direction, "$appName§splitter§${appInfo.applicationInfo.packageName}§splitter§$profile"
)
requireActivity().supportFragmentManager.popBackStack()
}
override fun onItemClick(appInfo: LauncherActivityInfo, profile: Int) {
showConfirmationDialog(appInfo, sharedPreferenceManager.getAppName(
appInfo.applicationInfo.packageName,
profile,
requireContext().packageManager.getApplicationLabel(appInfo.applicationInfo)
).toString(), profile)
}
}

View file

@ -0,0 +1,88 @@
package eu.ottop.yamlauncher.settings
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.os.UserHandle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.UIUtils
class HiddenAppsAdapter(
private val context: Context,
private var apps: MutableList<Triple<LauncherActivityInfo, UserHandle, Int>>,
private val itemClickListener: OnItemClickListener
) :
RecyclerView.Adapter<HiddenAppsAdapter.AppViewHolder>() {
private val sharedPreferenceManager = SharedPreferenceManager(context)
private val uiUtils = UIUtils(context)
interface OnItemClickListener {
fun onItemClick(appInfo: LauncherActivityInfo, profile: Int)
}
inner class AppViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val listItem: FrameLayout = itemView.findViewById(R.id.listItem)
val textView: TextView = listItem.findViewById(R.id.appName)
init {
textView.setOnClickListener {
val position = bindingAdapterPosition
val app = apps[position].first
itemClickListener.onItemClick(app, apps[position].third)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.app_item_layout, parent, false)
return AppViewHolder(view)
}
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
val app = apps[position]
if (app.third != 0) {
holder.textView.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(context.resources,
R.drawable.ic_work_app, null),null,null,null)
}
else {
holder.textView.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(context.resources,
R.drawable.ic_empty, null),null,null,null)
}
uiUtils.setAppAlignment(holder.textView)
uiUtils.setAppSize(holder.textView)
// Separate activity from Main so does not need special update
val appInfo = app.first.activityInfo.applicationInfo
holder.textView.text = sharedPreferenceManager.getAppName(
app.first.applicationInfo.packageName,
app.third,
context.packageManager.getApplicationLabel(appInfo)
)
holder.textView.visibility = View.VISIBLE
}
override fun getItemCount(): Int {
return apps.size
}
@SuppressLint("NotifyDataSetChanged")
fun updateApps(newApps: List<Triple<LauncherActivityInfo, UserHandle, Int>>) {
apps = newApps.toMutableList()
notifyDataSetChanged()
}
}

View file

@ -0,0 +1,165 @@
package eu.ottop.yamlauncher.settings
import android.app.AlertDialog
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.os.Bundle
import android.os.UserHandle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import eu.ottop.yamlauncher.utils.AppMenuEdgeFactory
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.AppUtils
import eu.ottop.yamlauncher.utils.StringUtils
import eu.ottop.yamlauncher.utils.UIUtils
import kotlinx.coroutines.launch
class HiddenAppsFragment : Fragment(), HiddenAppsAdapter.OnItemClickListener {
private lateinit var sharedPreferenceManager: SharedPreferenceManager
private var adapter: HiddenAppsAdapter? = null
private var stringUtils = StringUtils()
private lateinit var uiUtils: UIUtils
private lateinit var appUtils: AppUtils
private lateinit var launcherApps: LauncherApps
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_hidden_apps, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
launcherApps = requireContext().getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
uiUtils = UIUtils(requireContext())
appUtils = AppUtils(requireContext(), launcherApps)
sharedPreferenceManager = SharedPreferenceManager(requireContext())
lifecycleScope.launch {
val recyclerView = view.findViewById<RecyclerView>(R.id.hiddenAppRecycler)
val appMenuEdgeFactory = AppMenuEdgeFactory(requireActivity())
adapter = HiddenAppsAdapter(requireContext(), appUtils.getHiddenApps().toMutableList(), this@HiddenAppsFragment)
recyclerView.edgeEffectFactory = appMenuEdgeFactory
recyclerView.adapter = adapter
recyclerView.scrollToPosition(0)
val searchView = view.findViewById<TextInputEditText>(R.id.hiddenAppSearch)
uiUtils.setMenuTitleAlignment(view.findViewById(R.id.hiddenMenuTitle))
uiUtils.setSearchAlignment(searchView)
uiUtils.setSearchSize(searchView)
recyclerView.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
if (bottom - top > oldBottom - oldTop) {
searchView.clearFocus()
}
}
searchView.addTextChangedListener(object :
TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
lifecycleScope.launch {
filterItems(s.toString())
}
}
})
if (sharedPreferenceManager.isAutoKeyboardEnabled()) {
val imm =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
searchView.requestFocus()
imm.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT)
}
}
}
private suspend fun filterItems(query: String?) {
val cleanQuery = stringUtils.cleanString(query)
val newFilteredApps = mutableListOf<Triple<LauncherActivityInfo, UserHandle, Int>>()
val updatedApps = appUtils.getHiddenApps()
getFilteredApps(cleanQuery, newFilteredApps, updatedApps)
applySearch(newFilteredApps)
}
private fun getFilteredApps(cleanQuery: String?, newFilteredApps: MutableList<Triple<LauncherActivityInfo, UserHandle, Int>>, updatedApps: List<Triple<LauncherActivityInfo, UserHandle, Int>>) {
if (cleanQuery.isNullOrEmpty()) {
newFilteredApps.addAll(updatedApps)
} else {
updatedApps.forEach {
val cleanItemText = stringUtils.cleanString(sharedPreferenceManager.getAppName(
it.first.applicationInfo.packageName,
it.third,
requireContext().packageManager.getApplicationLabel(it.first.applicationInfo)
).toString())
if (cleanItemText != null) {
if (cleanItemText.contains(cleanQuery, ignoreCase = true)) {
newFilteredApps.add(it)
}
}
}
}
}
private fun applySearch(newFilteredApps: MutableList<Triple<LauncherActivityInfo, UserHandle, Int>>) {
adapter?.updateApps(newFilteredApps)
}
private fun showConfirmationDialog(appInfo: LauncherActivityInfo, appName: String, profile: Int) {
AlertDialog.Builder(requireContext()).apply {
setTitle("Confirmation")
setMessage("Are you sure you want to unhide $appName?")
setPositiveButton("Yes") { _, _ ->
lifecycleScope.launch {
performConfirmedAction(appInfo, profile)
}
}
setNegativeButton("Cancel") { _, _ ->
}
}.create().show()
}
private suspend fun performConfirmedAction(appInfo: LauncherActivityInfo, profile: Int) {
sharedPreferenceManager.setAppVisible(appInfo.applicationInfo.packageName, profile)
adapter?.updateApps(appUtils.getHiddenApps())
}
override fun onItemClick(appInfo: LauncherActivityInfo, profile: Int) {
showConfirmationDialog(appInfo, sharedPreferenceManager.getAppName(
appInfo.applicationInfo.packageName,
profile,
requireContext().packageManager.getApplicationLabel(appInfo.applicationInfo)
).toString(), profile)
}
}

View file

@ -0,0 +1,135 @@
package eu.ottop.yamlauncher.settings
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
import eu.ottop.yamlauncher.utils.AppMenuEdgeFactory
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.WeatherSystem
import eu.ottop.yamlauncher.utils.StringUtils
import eu.ottop.yamlauncher.utils.UIUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LocationFragment : Fragment(), LocationListAdapter.OnItemClickListener {
private var adapter: LocationListAdapter? = null
private lateinit var weatherSystem: WeatherSystem
private lateinit var sharedPreferenceManager: SharedPreferenceManager
private val stringUtils = StringUtils()
private lateinit var uiUtils: UIUtils
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_location, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
uiUtils = UIUtils(requireContext())
weatherSystem = WeatherSystem(requireContext())
sharedPreferenceManager = SharedPreferenceManager(requireContext())
val searchView = view.findViewById<TextInputEditText>(R.id.locationSearch)
var locationList = mutableListOf<Map<String, String>>()
stringUtils.setLink(requireActivity().findViewById(R.id.locationLink), getString(R.string.location_link))
lifecycleScope.launch(Dispatchers.IO) {
locationList = weatherSystem.getSearchedLocations(
searchView.text.toString()
)
}
adapter = LocationListAdapter(requireContext(), locationList, this)
val recyclerView = view.findViewById<RecyclerView>(R.id.locationRecycler)
val appMenuEdgeFactory = AppMenuEdgeFactory(requireActivity())
uiUtils.setMenuTitleAlignment(view.findViewById(R.id.locationMenuTitle))
uiUtils.setSearchAlignment(searchView)
uiUtils.setSearchSize(searchView)
recyclerView.edgeEffectFactory = appMenuEdgeFactory
recyclerView.adapter = adapter
recyclerView.scrollToPosition(0)
recyclerView.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
if (bottom - top > oldBottom - oldTop) {
searchView.clearFocus()
}
}
searchView.addTextChangedListener(object :
TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
// Filtering is not needed since we are creating a list with data pulled from the Open-Meteo api instead of searching an existing list
lifecycleScope.launch(Dispatchers.IO){
val locations = weatherSystem.getSearchedLocations(
searchView.text.toString()
)
withContext(Dispatchers.Main) {
adapter?.updateLocations(locations)
}
}
}
})
if (sharedPreferenceManager.isAutoKeyboardEnabled()) {
val imm =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
searchView.requestFocus()
imm.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT)
}
}
private fun showConfirmationDialog(appName: String?, latitude: String?, longitude: String?) {
AlertDialog.Builder(requireContext()).apply {
setTitle("Confirmation")
setMessage("Are you sure you want to select $appName?")
setPositiveButton("Yes") { _, _ ->
performConfirmedAction(appName, latitude, longitude)
}
setNegativeButton("Cancel") { _, _ ->
}
}.create().show()
}
private fun performConfirmedAction(appName: String?, latitude: String?, longitude: String?) {
sharedPreferenceManager.setWeatherLocation("latitude=${latitude}&longitude=${longitude}", appName)
requireActivity().supportFragmentManager.popBackStack()
}
override fun onItemClick(name: String?, latitude: String?, longitude: String?) {
showConfirmationDialog(name, latitude, longitude)
}
}

View file

@ -0,0 +1,74 @@
package eu.ottop.yamlauncher.settings
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.utils.UIUtils
class LocationListAdapter(
private val context: Context,
private var locations: MutableList<Map<String, String>>,
private val itemClickListener: OnItemClickListener
) :
RecyclerView.Adapter<LocationListAdapter.AppViewHolder>() {
private val uiUtils = UIUtils(context)
interface OnItemClickListener {
fun onItemClick(name: String?, latitude: String?, longitude: String?)
}
inner class AppViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val listItem: ConstraintLayout = itemView.findViewById(R.id.locationPlace)
val textView: TextView = listItem.findViewById(R.id.locationName)
val regionText: TextView = listItem.findViewById(R.id.regionName)
init {
listItem.setOnClickListener {
val position = bindingAdapterPosition
val name = locations[position]["name"]
val latitude = locations[position]["latitude"]
val longitude = locations[position]["longitude"]
itemClickListener.onItemClick(name, latitude, longitude)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.location_item_layout, parent, false)
return AppViewHolder(view)
}
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
val location = locations[position]
uiUtils.setAppAlignment(holder.textView, null, holder.regionText)
uiUtils.setAppSize(holder.textView, null, holder.regionText)
holder.textView.text = location["name"]
holder.regionText.text = context.getString(R.string.region_text, location["region"], location["country"])
holder.textView.visibility = View.VISIBLE
}
override fun getItemCount(): Int {
return locations.size
}
@SuppressLint("NotifyDataSetChanged")
fun updateLocations(newApps: MutableList<Map<String, String>>) {
locations.clear()
locations = newApps
notifyDataSetChanged()
}
}

View file

@ -0,0 +1,22 @@
package eu.ottop.yamlauncher.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import eu.ottop.yamlauncher.R
import eu.ottop.yamlauncher.databinding.ActivitySettingsBinding
class SettingsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
supportFragmentManager
.beginTransaction()
.replace(R.id.settingsLayout, SettingsFragment())
.commit()
}
}

View file

@ -0,0 +1,109 @@
package eu.ottop.yamlauncher.settings
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import eu.ottop.yamlauncher.R
class SettingsFragment : PreferenceFragmentCompat() {
private var manualLocationPref: Preference? = null
private var leftSwipePref: Preference? = null
private var rightSwipePref: Preference? = null
private lateinit var sharedPreferenceManager: SharedPreferenceManager
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
sharedPreferenceManager = SharedPreferenceManager(requireContext())
val gpsLocationPref = findPreference<SwitchPreference?>("gpsLocation")
manualLocationPref = findPreference("manualLocation")
leftSwipePref = findPreference("leftSwipeApp")
rightSwipePref = findPreference("rightSwipeApp")
val aboutPref = findPreference<Preference?>("aboutPage")
val hiddenPref = findPreference<Preference?>("hiddenApps")
val homePref = findPreference<Preference?>("defaultHome")
// Only enable manual location when gps location is disabled
if (gpsLocationPref != null && manualLocationPref != null) {
manualLocationPref?.isEnabled = !gpsLocationPref.isChecked
gpsLocationPref.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
val isGpsEnabled = newValue as Boolean
manualLocationPref?.isEnabled = !isGpsEnabled
true
}
manualLocationPref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.settingsLayout, LocationFragment())
.addToBackStack(null)
.commit()
true
}
}
hiddenPref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.settingsLayout, HiddenAppsFragment())
.addToBackStack(null)
.commit()
true }
leftSwipePref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.settingsLayout, GestureAppsFragment("left"))
.addToBackStack(null)
.commit()
true }
rightSwipePref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.settingsLayout, GestureAppsFragment("right"))
.addToBackStack(null)
.commit()
true }
aboutPref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
requireActivity().supportFragmentManager
.beginTransaction()
.replace(R.id.settingsLayout, AboutFragment())
.addToBackStack(null)
.commit()
true }
homePref?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent(Settings.ACTION_HOME_SETTINGS)
if (intent.resolveActivity(requireContext().packageManager) != null) {
startActivity(intent)
} else {
Toast.makeText(requireContext(), "Unable to launch settings", Toast.LENGTH_SHORT).show()
}
true }
}
override fun onResume() {
super.onResume()
manualLocationPref?.summary = sharedPreferenceManager.getWeatherRegion()
leftSwipePref?.summary = sharedPreferenceManager.getGestureName("left")
rightSwipePref?.summary = sharedPreferenceManager.getGestureName("right")
}
}

View file

@ -0,0 +1,179 @@
package eu.ottop.yamlauncher.settings
import android.content.Context
import android.graphics.Color
import android.widget.TextView
import androidx.preference.PreferenceManager
class SharedPreferenceManager (context: Context) {
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// General UI
fun getBgColor(): Int {
return Color.parseColor(preferences.getString("bgColor", "#00000000"))
}
fun getTextColor(): Int {
return Color.parseColor(preferences.getString("textColor", "#FFF3F3F3"))
}
fun isBarVisible(): Boolean {
return preferences.getBoolean("barVisibility", false)
}
fun getAnimationSpeed(): Long {
val animSpeed = preferences.getString("animationSpeed", "200")?.toLong()
if (animSpeed != null) {
return animSpeed
}
return 200
}
// Home Screen
fun getClockAlignment(): String? {
return preferences.getString("clockAlignment", "left")
}
fun getClockSize(): String? {
return preferences.getString("clockSize","medium")
}
fun getDateSize(): String? {
return preferences.getString("dateSize", "medium")
}
fun setShortcut(textView: TextView, packageName: String, profile: Int) {
val editor = preferences.edit()
editor.putString("shortcut${textView.id}", "$packageName§splitter§$profile§splitter§${textView.text}")
editor.apply()
}
fun getShortcut(textView: TextView): List<String>? {
val value = preferences.getString("shortcut${textView.id}", "e§splitter§e")
return value?.split("§splitter§")
}
fun getShortcutNumber(): Int? {
return preferences.getString("shortcutNo", "4")?.toInt()
}
fun getShortcutAlignment(): String? {
return preferences.getString("shortcutAlignment", "left")
}
fun getShortcutSize(): String? {
return preferences.getString("shortcutSize", "medium")
}
fun isBatteryEnabled(): Boolean {
return preferences.getBoolean("batteryEnabled", false)
}
// Weather
fun isWeatherEnabled(): Boolean {
return preferences.getBoolean("weatherEnabled", false)
}
fun isWeatherGPS(): Boolean {
return preferences.getBoolean("gpsLocation", false)
}
fun setWeatherLocation(location: String, region: String?) {
val editor = preferences.edit()
editor.putString("location", location)
editor.putString("locationRegion", region)
editor.apply()
}
fun getWeatherLocation(): String? {
return preferences.getString("location", "")
}
fun getWeatherRegion(): String? {
return preferences.getString("locationRegion", "")
}
fun getTempUnits(): String? {
return preferences.getString("tempUnits", "celsius")
}
// Gestures
fun setGestures(direction: String, appName: String?) {
val editor = preferences.edit()
editor.putString("${direction}SwipeApp", appName)
editor.apply()
}
fun getGestureName(direction: String) : String? {
val name = preferences.getString("${direction}SwipeApp", "")?.split("§splitter§")
return name?.get(0)
}
fun getGestureInfo(direction: String) : List<String>? {
return preferences.getString("${direction}SwipeApp", "")?.split("§splitter§")
}
fun isGestureEnabled(direction: String) : Boolean {
return preferences.getBoolean("${direction}Swipe", false)
}
fun isDoubleTapEnabled(): Boolean {
return preferences.getBoolean("doubleTap", false)
}
// Application Menu
fun getAppAlignment(): String? {
return preferences.getString("appMenuAlignment", "left")
}
fun getAppSize(): String? {
return preferences.getString("appMenuSize", "medium")
}
fun getSearchAlignment(): String? {
return preferences.getString("searchAlignment", "left")
}
fun getSearchSize(): String? {
return preferences.getString("searchSize", "medium")
}
fun isAutoKeyboardEnabled(): Boolean {
return preferences.getBoolean("autoKeyboard", false)
}
// Hidden Apps
fun setAppHidden(packageName: String, profile: Int, hidden: Boolean) {
val editor = preferences.edit()
editor.putBoolean("hidden$packageName-$profile", hidden)
editor.apply()
}
fun isAppHidden(packageName: String, profile: Int): Boolean {
return preferences.getBoolean("hidden$packageName-$profile", false) // Default to false (visible)
}
fun setAppVisible(packageName: String, profile: Int) {
val editor = preferences.edit()
editor.remove("hidden$packageName-$profile")
editor.apply()
}
//Renaming apps
fun setAppName(packageName: String, profile: Int, newName: String) {
val editor = preferences.edit()
editor.putString("name$packageName-$profile", newName)
editor.apply()
}
fun getAppName(packageName: String, profile: Int, appName: CharSequence): CharSequence? {
return preferences.getString("name$packageName-$profile", appName.toString())
}
fun resetAppName(packageName: String, profile: Int) {
val editor = preferences.edit()
editor.remove("name$packageName-$profile")
editor.apply()
}
}

View file

@ -0,0 +1,29 @@
package eu.ottop.yamlauncher.tasks
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import eu.ottop.yamlauncher.MainActivity
class BatteryReceiver(private val activity: MainActivity) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val batteryPct = level * 100 / scale.toFloat()
activity.modifyDate("${batteryPct.toInt()}%", 3) // Add battery to the date
}
}
companion object {
fun register(context: Context, activity: MainActivity): BatteryReceiver {
val receiver = BatteryReceiver(activity)
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
context.registerReceiver(receiver, filter)
return receiver
}
}
}

View file

@ -0,0 +1,26 @@
package eu.ottop.yamlauncher.tasks
import android.accessibilityservice.AccessibilityService
import android.content.Intent
import android.view.accessibility.AccessibilityEvent
class ScreenLockService : AccessibilityService() {
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
}
override fun onInterrupt() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action == "LOCK_SCREEN") {
performLockScreen()
}
stopSelf()
return super.onStartCommand(intent, flags, startId)
}
private fun performLockScreen() {
performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN)
}
}

View file

@ -0,0 +1,149 @@
package eu.ottop.yamlauncher.utils
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.View
import eu.ottop.yamlauncher.settings.SharedPreferenceManager
class Animations (context: Context) {
private val sharedPreferenceManager = SharedPreferenceManager(context)
// fadeViewIn and fadeViewOut are for smaller item transitions, such as the action menu
fun fadeViewIn(view: View) {
view.fadeIn()
}
fun fadeViewOut(view: View) {
view.fadeOut()
}
fun showHome(homeView: View, appView: View) {
appView.slideOutToBottom()
homeView.fadeIn()
}
fun showApps(homeView: View, appView: View) {
appView.slideInFromBottom()
homeView.fadeOut()
}
fun backgroundIn(activity: Activity) {
val originalColor = sharedPreferenceManager.getBgColor()
// Only animate darkness onto the transparent background
if (originalColor == Color.parseColor("#00000000")) {
val newColor = Color.parseColor("#3F000000")
val colorDrawable = ColorDrawable(originalColor)
activity.window.setBackgroundDrawable(colorDrawable)
val backgroundColorAnimator = ValueAnimator.ofObject(ArgbEvaluator(), originalColor, newColor)
backgroundColorAnimator.addUpdateListener { animator ->
colorDrawable.color = animator.animatedValue as Int
}
val duration = sharedPreferenceManager.getAnimationSpeed()
backgroundColorAnimator.duration = duration
backgroundColorAnimator.start()
} else {
return
}
}
fun backgroundOut(activity: Activity) {
val newColor = sharedPreferenceManager.getBgColor()
// Only animate darkness onto the transparent background
if (newColor == Color.parseColor("#00000000")) {
val originalColor = Color.parseColor("#3F000000")
val colorDrawable = ColorDrawable(originalColor)
activity.window.setBackgroundDrawable(colorDrawable)
val backgroundColorAnimator = ValueAnimator.ofObject(ArgbEvaluator(), originalColor, newColor)
backgroundColorAnimator.addUpdateListener { animator ->
colorDrawable.color = animator.animatedValue as Int
}
val duration = sharedPreferenceManager.getAnimationSpeed()
backgroundColorAnimator.duration = duration
backgroundColorAnimator.start()
} else {
return
}
}
private fun View.slideInFromBottom() {
if (visibility != View.VISIBLE) {
translationY = height.toFloat()/5
scaleY = 1.2f
alpha = 0f
visibility = View.VISIBLE
val duration = sharedPreferenceManager.getAnimationSpeed()
animate()
.translationY(0f)
.scaleY(1f)
.alpha(1f)
.setDuration(duration)
.setListener(null)
}
}
private fun View.slideOutToBottom() {
if (visibility == View.VISIBLE) {
val duration = sharedPreferenceManager.getAnimationSpeed()
animate()
.translationY(height.toFloat() / 5)
.scaleY(1.2f)
.alpha(0f)
.setDuration(duration/2)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
visibility = View.INVISIBLE
}
})
}
}
private fun View.fadeIn() {
if (visibility != View.VISIBLE) {
alpha = 0f
translationY = -height.toFloat()/100
visibility = View.VISIBLE
val duration = sharedPreferenceManager.getAnimationSpeed()
animate()
.alpha(1f)
.translationY(0f)
.setDuration(duration)
.setListener(null)
}
}
private fun View.fadeOut() {
if (visibility == View.VISIBLE) {
val duration = sharedPreferenceManager.getAnimationSpeed()
animate()
.alpha(0f)
.translationY(-height.toFloat()/100)
.setDuration(duration/2)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
visibility = View.GONE
}
})
}
}
}

View file

@ -0,0 +1,31 @@
package eu.ottop.yamlauncher.utils
import android.app.Activity
import android.widget.EdgeEffect
import androidx.recyclerview.widget.RecyclerView
class AppMenuEdgeFactory(private val activity: Activity) : RecyclerView.EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
return AppMenuEdgeEffect(activity)
}
inner class AppMenuEdgeEffect(activity: Activity) : EdgeEffect(activity) {
//This just speeds up the animation when the scrolling hits the edge so that the app menu can be exited sooner
private val animationSpeedFactor = 0.75f
override fun onAbsorb(velocity: Int) {
super.onAbsorb((velocity * animationSpeedFactor).toInt())
}
override fun onPull(deltaDistance: Float, displacement: Float) {
super.onPull(deltaDistance * animationSpeedFactor, displacement)
}
override fun onPullDistance(deltaDistance: Float, displacement: Float): Float {
return super.onPullDistance(deltaDistance * animationSpeedFactor, displacement)
}
}
}

View file

@ -0,0 +1,33 @@
package eu.ottop.yamlauncher.utils
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.ottop.yamlauncher.MainActivity
class AppMenuLinearLayoutManager(private val activity: MainActivity) : LinearLayoutManager(activity) {
private var firstVisibleItemPosition = 0
private var scrollStarted = false
fun setScrollInfo() {
firstVisibleItemPosition = findFirstCompletelyVisibleItemPosition()
scrollStarted = true
}
override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler?, state: RecyclerView.State?): Int {
val scrollRange = super.scrollVerticallyBy(dy, recycler, state)
val overscroll: Int = dy - scrollRange
// If the user scrolls up when already on top, go back to home. Only if the keyboard isn't open, though
if (overscroll < 0 && (firstVisibleItemPosition == 0 || firstVisibleItemPosition < 0) && scrollStarted) {
activity.backToHome()
}
if (scrollStarted) {
scrollStarted = false
}
return scrollRange
}
}

View file

@ -0,0 +1,90 @@
package eu.ottop.yamlauncher.utils
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.os.UserHandle
import android.widget.Toast
import eu.ottop.yamlauncher.settings.SharedPreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AppUtils(private val context: Context, private val launcherApps: LauncherApps) {
private val sharedPreferenceManager = SharedPreferenceManager(context)
suspend fun getInstalledApps(): List<Triple<LauncherActivityInfo, UserHandle, Int>> {
val allApps = mutableListOf<Triple<LauncherActivityInfo, UserHandle, Int>>()
var sortedApps = listOf<Triple<LauncherActivityInfo, UserHandle, Int>>()
withContext(Dispatchers.Default) {
for (i in launcherApps.profiles.indices) { // Check apps on both, normal and work profiles
launcherApps.getActivityList(null, launcherApps.profiles[i]).forEach { app ->
if (!sharedPreferenceManager.isAppHidden( // Only include the app if it isn't set as hidden
app.applicationInfo.packageName,
i
) && app.applicationInfo.packageName != context.applicationInfo.packageName // Hide the launcher itself
) {
allApps.add(Triple(app, launcherApps.profiles[i], i)) // The i variable gets used to determine whether an app is in the personal profile or work profile
}
}
}
// Sort apps by name
sortedApps = allApps.sortedBy {
sharedPreferenceManager.getAppName(
it.first.applicationInfo.packageName,
it.third,
context.packageManager.getApplicationLabel(it.first.applicationInfo)
).toString().lowercase()
}
}
return sortedApps
}
// Get hidden apps for the hidden apps settings
suspend fun getHiddenApps(): List<Triple<LauncherActivityInfo, UserHandle, Int>> {
val allApps = mutableListOf<Triple<LauncherActivityInfo, UserHandle, Int>>()
var sortedApps = listOf<Triple<LauncherActivityInfo, UserHandle, Int>>()
withContext(Dispatchers.Default) {
for (i in launcherApps.profiles.indices) {
launcherApps.getActivityList(null, launcherApps.profiles[i]).forEach { app ->
if (sharedPreferenceManager.isAppHidden(app.applicationInfo.packageName, i)) {
allApps.add(Triple(app, launcherApps.profiles[i], i))
}
}
}
//Sort apps by name
sortedApps = allApps.sortedBy {
sharedPreferenceManager.getAppName(
it.first.applicationInfo.packageName,
it.third,
context.packageManager.getApplicationLabel(it.first.applicationInfo)
).toString().lowercase()
}
}
return sortedApps
}
fun getAppInfo(
packageName: String,
profile: Int
): ApplicationInfo? {
return try {
launcherApps.getApplicationInfo(packageName, 0, launcherApps.profiles[profile])
} catch (_: Exception) {
null
}
}
fun launchApp(appInfo: LauncherActivityInfo, userHandle: UserHandle) {
val mainActivity = launcherApps.getActivityList(appInfo.applicationInfo.packageName, userHandle).firstOrNull()
if (mainActivity != null) {
launcherApps.startMainActivity(mainActivity.componentName, userHandle, null, null)
} else {
Toast.makeText(context, "Cannot launch app", Toast.LENGTH_SHORT).show()
}
}
}

Some files were not shown because too many files have changed in this diff Show more