mirror of
https://github.com/He4eT/yamf_launcher.git
synced 2026-05-05 01:47:24 +00:00
Fixed returning from app menu and refactoring
This commit is contained in:
parent
1a748ce709
commit
5cdd7bca2e
8 changed files with 183 additions and 516 deletions
|
|
@ -16,10 +16,11 @@ import android.widget.EditText
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatButton
|
||||
import eu.ottop.yamlauncher.databinding.ActivityAppMenuBinding
|
||||
import eu.ottop.yamlauncher.databinding.ActivityMainBinding
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AppActionMenu {
|
||||
|
||||
|
|
@ -28,7 +29,6 @@ class AppActionMenu {
|
|||
|
||||
fun setActionListeners(
|
||||
activity: MainActivity,
|
||||
uiScope: CoroutineScope,
|
||||
binding: ActivityMainBinding,
|
||||
textView: TextView,
|
||||
editLayout: LinearLayout,
|
||||
|
|
@ -106,13 +106,14 @@ class AppActionMenu {
|
|||
workProfile,
|
||||
editText.text.toString()
|
||||
)
|
||||
|
||||
val newPosition = appUtils.getInstalledApps(activity)
|
||||
.indexOfFirst { it.first.applicationInfo.packageName == appInfo.packageName && it.second.second == workProfile }
|
||||
|
||||
activity.updateItem(position, app)
|
||||
activity.moveItem(position, newPosition)
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val newPosition = appUtils.getInstalledApps(activity)
|
||||
.indexOfFirst { it.first.applicationInfo.packageName == appInfo.packageName && it.second.second == workProfile }
|
||||
withContext(Dispatchers.Main) {
|
||||
activity.updateItem(position, app)
|
||||
activity.moveItem(position, newPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return@setOnEditorActionListener true
|
||||
|
|
@ -129,11 +130,15 @@ class AppActionMenu {
|
|||
app.first.applicationInfo.packageName,
|
||||
app.second.second
|
||||
)
|
||||
val newPosition = appUtils.getInstalledApps(activity)
|
||||
.indexOfFirst { it.first.applicationInfo.packageName == appInfo.packageName && it.second.second == workProfile }
|
||||
activity.updateItem(position, app)
|
||||
activity.moveItem(position, newPosition)
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val newPosition = appUtils.getInstalledApps(activity)
|
||||
.indexOfFirst { it.first.applicationInfo.packageName == appInfo.packageName && it.second.second == workProfile }
|
||||
withContext(Dispatchers.Main) {
|
||||
activity.updateItem(position, app)
|
||||
activity.moveItem(position, newPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +146,10 @@ class AppActionMenu {
|
|||
editLayout.visibility = View.GONE
|
||||
textView.visibility = View.GONE
|
||||
actionMenu.visibility = View.GONE
|
||||
sharedPreferenceManager.setAppHidden(activity, appInfo.packageName, workProfile, true)
|
||||
activity.manualRefresh()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
sharedPreferenceManager.setAppHidden(activity, appInfo.packageName, workProfile, true)
|
||||
activity.refreshAppMenu()
|
||||
}
|
||||
}
|
||||
|
||||
actionMenu.findViewById<TextView>(R.id.close).setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -1,349 +0,0 @@
|
|||
package eu.ottop.yamlauncher
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.util.Log
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.ottop.yamlauncher.databinding.ActivityAppMenuBinding
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class AppMenuActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, AppMenuAdapter.OnShortcutListener, AppMenuAdapter.OnItemLongClickListener {
|
||||
/*
|
||||
private lateinit var binding: ActivityAppMenuBinding
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var searchView: EditText
|
||||
private lateinit var adapter: AppMenuAdapter
|
||||
private var job: Job? = null
|
||||
private var appActionMenu = AppActionMenu()
|
||||
private lateinit var launcherApps: LauncherApps
|
||||
private lateinit var installedApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>
|
||||
|
||||
private val sharedPreferenceManager = SharedPreferenceManager()
|
||||
private val appUtils = AppUtils()
|
||||
|
||||
private lateinit var menuMode: String
|
||||
|
||||
companion object {
|
||||
private lateinit var callback: (Pair<Pair<String, Int>, Pair<LauncherActivityInfo, UserHandle>>) -> Unit
|
||||
private const val MENU_MODE = "app"
|
||||
private lateinit var currentApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>
|
||||
fun start(context: Context, currentApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>, param1: String = "app", callback: (Pair<Pair<String, Int>, Pair<LauncherActivityInfo, UserHandle>>) -> Unit) {
|
||||
val intent = Intent(context, AppMenuActivity::class.java).apply {
|
||||
putExtra(MENU_MODE, param1)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
|
||||
this.callback = callback
|
||||
this.currentApps = currentApps
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
menuMode = intent.getStringExtra(MENU_MODE) ?: "app"
|
||||
|
||||
binding = ActivityAppMenuBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(null)
|
||||
if (menuMode == "shortcut") {
|
||||
binding.menutitle.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
searchView = findViewById(R.id.searchView)
|
||||
|
||||
recyclerView = findViewById(R.id.recycler_view)
|
||||
recyclerView.scrollToPosition(0)
|
||||
installedApps = currentApps
|
||||
val newApps = mutableListOf<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>()
|
||||
newApps.addAll(installedApps)
|
||||
adapter = AppMenuAdapter(this@AppMenuActivity, newApps, this, this,this, menuMode)
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
setupSearch()
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finish()
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onItemClick(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(this, "Cannot launch app", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShortcut(appInfo: LauncherActivityInfo, userHandle: UserHandle, textView: TextView, userProfile: Int) {
|
||||
callback.invoke(Pair(Pair(textView.text.toString(), userProfile), Pair(appInfo, userHandle)))
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onItemLongClick(
|
||||
appInfo: LauncherActivityInfo,
|
||||
userHandle: UserHandle,
|
||||
userProfile: Int,
|
||||
textView: TextView,
|
||||
actionMenuLayout: LinearLayout,
|
||||
editView: LinearLayout,
|
||||
position: Int
|
||||
) {
|
||||
textView.visibility = View.INVISIBLE
|
||||
actionMenuLayout.visibility = View.VISIBLE
|
||||
val mainActivity =
|
||||
launcherApps.getActivityList(appInfo.applicationInfo.packageName, userHandle)
|
||||
.firstOrNull()
|
||||
appActionMenu.setActionListeners(
|
||||
this@AppMenuActivity,
|
||||
CoroutineScope(Dispatchers.Main),
|
||||
binding,
|
||||
textView,
|
||||
editView,
|
||||
actionMenuLayout,
|
||||
searchView,
|
||||
appInfo.applicationInfo,
|
||||
userHandle,
|
||||
userProfile,
|
||||
launcherApps,
|
||||
mainActivity,
|
||||
position
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupSearch() {
|
||||
binding.root.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
|
||||
if (bottom - top > oldBottom - oldTop) {
|
||||
searchView.clearFocus()
|
||||
if (searchView.text.isNullOrEmpty()) {
|
||||
job?.cancel()
|
||||
startTask()
|
||||
}
|
||||
}
|
||||
else {
|
||||
job?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
filterItems(searchView.text.toString())
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private fun filterItems(query: String?) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val cleanQuery = query?.clean()
|
||||
val newFilteredApps = mutableListOf<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>()
|
||||
val updatedApps = appUtils.getInstalledApps(this@AppMenuActivity)
|
||||
|
||||
if (cleanQuery.isNullOrEmpty()) {
|
||||
manualRefresh()
|
||||
newFilteredApps.addAll(installedApps)
|
||||
} else {
|
||||
updatedApps.forEach {
|
||||
val cleanItemText = sharedPreferenceManager.getAppName(this@AppMenuActivity, it.first.applicationInfo.packageName, it.second.second, it.first.applicationInfo.loadLabel(packageManager)).toString().clean()
|
||||
if (cleanItemText.contains(cleanQuery, ignoreCase = true)) {
|
||||
newFilteredApps.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val changes = detectChanges(installedApps, newFilteredApps)
|
||||
installedApps = newFilteredApps
|
||||
withContext(Dispatchers.Main) {
|
||||
applyChanges(changes, installedApps)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun String.clean(): String {
|
||||
return this.replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
job?.cancel()
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
startTask()
|
||||
|
||||
// Keyboard is sometimes open when going back to the app, so close it.
|
||||
val imm =
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(binding.root.windowToken, 0)
|
||||
}
|
||||
|
||||
private fun startTask() {
|
||||
job = CoroutineScope(Dispatchers.Default).launch {
|
||||
while (true) {
|
||||
manualRefresh()
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun manualRefresh() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val updatedApps = appUtils.getInstalledApps(this@AppMenuActivity)
|
||||
val changes = detectChanges(installedApps, updatedApps)
|
||||
installedApps = updatedApps
|
||||
withContext(Dispatchers.Main) {
|
||||
applyChanges(changes, installedApps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun detectChanges(oldList: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>, newList: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>): List<Change> {
|
||||
val changes = mutableListOf<Change>()
|
||||
val removalChanges = mutableListOf<Change>()
|
||||
val oldSet = oldList.map { Pair(it.first.applicationInfo.packageName, it.second.second) }.toSet()
|
||||
val newSet = newList.map { Pair(it.first.applicationInfo.packageName, it.second.second) }.toSet()
|
||||
|
||||
//Detect updates
|
||||
oldList.forEachIndexed { index, oldItem ->
|
||||
if (newSet.contains(Pair(oldItem.first.applicationInfo.packageName, oldItem.second.second))) {
|
||||
val newIndex = newList.indexOfFirst { it.first.applicationInfo.packageName == oldItem.first.applicationInfo.packageName && it.second.second == oldItem.second.second }
|
||||
if (oldItem.first.componentName != newList[newIndex].first.componentName) {
|
||||
changes.add(Change(ChangeType.UPDATE, index))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Detect insertions
|
||||
newList.forEachIndexed { index, newItem ->
|
||||
if (!oldSet.contains(Pair(newItem.first.applicationInfo.packageName, newItem.second.second))) {
|
||||
changes.add(Change(ChangeType.INSERT, index))
|
||||
}
|
||||
}
|
||||
|
||||
// Detect removals
|
||||
oldList.forEachIndexed { index, oldItem ->
|
||||
if (!newSet.contains(Pair(oldItem.first.applicationInfo.packageName, oldItem.second.second))) {
|
||||
removalChanges.add(Change(ChangeType.REMOVE, index))
|
||||
}
|
||||
}
|
||||
|
||||
changes.addAll(removalChanges.reversed())
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
private fun applyChanges(changes: List<Change>, updatedApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>) {
|
||||
changes.forEach { change ->
|
||||
when (change.type) {
|
||||
ChangeType.INSERT -> {
|
||||
insertItem(change.position, updatedApps[change.position])
|
||||
}
|
||||
ChangeType.REMOVE -> {
|
||||
try {
|
||||
removeItem(change.position)
|
||||
}
|
||||
catch (_: IndexOutOfBoundsException) {
|
||||
}
|
||||
}
|
||||
ChangeType.UPDATE -> {
|
||||
updateItem(change.position, updatedApps[change.position])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertItem(position: Int, app: Pair<LauncherActivityInfo, Pair<UserHandle, Int>>) {
|
||||
adapter.addApp(position, app)
|
||||
adapter.notifyItemInserted(position)
|
||||
}
|
||||
private fun removeItem(position: Int) {
|
||||
adapter.removeApp(position)
|
||||
adapter.notifyItemRemoved(position)
|
||||
}
|
||||
|
||||
fun updateItem(position: Int, app: Pair<LauncherActivityInfo, Pair<UserHandle, Int>>) {
|
||||
adapter.updateApp(position, app)
|
||||
adapter.notifyItemChanged(position)
|
||||
}
|
||||
|
||||
fun moveItem(position: Int, newPosition: Int) {
|
||||
Log.d("Movestatus","MOVED")
|
||||
adapter.moveApp(position, newPosition)
|
||||
adapter.notifyItemMoved(position, newPosition)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Change(val type: ChangeType, val position: Int, val newPosition: Int = 0)
|
||||
|
||||
enum class ChangeType {
|
||||
INSERT, REMOVE, UPDATE*/
|
||||
override fun onItemClick(appInfo: LauncherActivityInfo, userHandle: UserHandle) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onItemLongClick(
|
||||
appInfo: LauncherActivityInfo,
|
||||
userHandle: UserHandle,
|
||||
userProfile: Int,
|
||||
textView: TextView,
|
||||
actionMenuLayout: LinearLayout,
|
||||
editView: LinearLayout,
|
||||
position: Int
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onShortcut(
|
||||
appInfo: LauncherActivityInfo,
|
||||
userHandle: UserHandle,
|
||||
textView: TextView,
|
||||
userProfile: Int,
|
||||
shortcutView: TextView
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +1,27 @@
|
|||
package eu.ottop.yamlauncher
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.widget.EdgeEffect
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class AppMenuEdgeFactory(private val activity: MainActivity) : RecyclerView.EdgeEffectFactory() {
|
||||
|
||||
private var isScrollingUp = false
|
||||
private val scrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
isScrollingUp = dy < 0
|
||||
}
|
||||
|
||||
}
|
||||
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
|
||||
view.addOnScrollListener(scrollListener)
|
||||
return AppMenuEdgeEffect(activity)
|
||||
}
|
||||
|
||||
inner class AppMenuEdgeEffect(private val activity: MainActivity) : EdgeEffect(activity) {
|
||||
private val animationSpeedFactor = 0.5f
|
||||
private val pullDistanceThreshold = 0.03f // Set a suitable threshold
|
||||
private val debounceInterval = 100L // Milliseconds
|
||||
private var lastActionTime = 0L
|
||||
|
||||
inner class AppMenuEdgeEffect(activity: MainActivity) : EdgeEffect(activity) {
|
||||
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)
|
||||
if (shouldTriggerAction(deltaDistance) && isScrollingUp) {
|
||||
super.onPull(deltaDistance * animationSpeedFactor, displacement)
|
||||
if (activity.recyclerAtTop()) {
|
||||
activity.showHome()
|
||||
}
|
||||
} else {
|
||||
super.onPull(deltaDistance * animationSpeedFactor, displacement)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPullDistance(deltaDistance: Float, displacement: Float): Float {
|
||||
return super.onPullDistance(deltaDistance * animationSpeedFactor, displacement)
|
||||
}
|
||||
|
||||
private fun shouldTriggerAction(deltaDistance: Float): Boolean {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
return deltaDistance > pullDistanceThreshold && (currentTime - lastActionTime > debounceInterval).also {
|
||||
if (it) lastActionTime = currentTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package eu.ottop.yamlauncher
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
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 (overscroll < 0 && firstVisibleItemPosition == 0 && scrollStarted) {
|
||||
activity.showHome()
|
||||
}
|
||||
|
||||
if (scrollStarted) {
|
||||
scrollStarted = false
|
||||
}
|
||||
|
||||
return scrollRange
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ import android.os.UserHandle
|
|||
import android.provider.MediaStore
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
|
@ -58,8 +57,12 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
private var appActionMenu = AppActionMenu()
|
||||
private val sharedPreferenceManager = SharedPreferenceManager()
|
||||
private val appUtils = AppUtils()
|
||||
private val appMenuLinearLayoutManager = AppMenuLinearLayoutManager(this@MainActivity)
|
||||
private val appMenuEdgeFactory = AppMenuEdgeFactory(this@MainActivity)
|
||||
|
||||
private val swipeThreshold = 100
|
||||
private val swipeVelocityThreshold = 100
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
|
@ -75,40 +78,91 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
showHome()
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
showHome()
|
||||
super.onNewIntent(intent)
|
||||
|
||||
}
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
job?.cancel()
|
||||
|
||||
}
|
||||
|
||||
fun recyclerAtTop() : Boolean {
|
||||
return recyclerView.scrollY == 0
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
startTask()
|
||||
|
||||
// Keyboard is sometimes open when going back to the app, so close it.
|
||||
val imm =
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(binding.root.windowToken, 0)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
gestureDetector.onTouchEvent(event)
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onFling(
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float
|
||||
): Boolean {
|
||||
if (e1 != null) {
|
||||
val deltaY = e2.y - e1.y
|
||||
val deltaX = e2.x - e1.x
|
||||
|
||||
// Detect swipe up
|
||||
if (deltaY < -swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
|
||||
openAppMenuActivity()
|
||||
return true
|
||||
}
|
||||
|
||||
// Detect swipe down
|
||||
else if (deltaY > swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Detect swipe left
|
||||
else if (deltaX < -swipeThreshold && abs(velocityX) > swipeVelocityThreshold){
|
||||
startActivity(cameraIntent)
|
||||
}
|
||||
|
||||
// Detect swipe right
|
||||
else if (deltaX > -swipeThreshold && abs(velocityX) > swipeVelocityThreshold) {
|
||||
startActivity(phoneIntent)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun setupApps() {
|
||||
handleListItems()
|
||||
|
||||
handleListItems()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
installedApps = appUtils.getInstalledApps(this@MainActivity)
|
||||
|
||||
val newApps = mutableListOf<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>()
|
||||
newApps.addAll(installedApps)
|
||||
val newApps = installedApps.toMutableList()
|
||||
|
||||
setupRecyclerView(newApps)
|
||||
|
||||
searchView = findViewById(R.id.searchView)
|
||||
setupSearch()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setupRecyclerView(newApps: MutableList<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>) {
|
||||
adapter = AppMenuAdapter(this@MainActivity, newApps, this@MainActivity, this@MainActivity, this@MainActivity)
|
||||
withContext(Dispatchers.Main) {
|
||||
recyclerView = findViewById(R.id.recycler_view)
|
||||
recyclerView.edgeEffectFactory = appMenuEdgeFactory
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.scrollToPosition(0)
|
||||
}
|
||||
searchView = findViewById(R.id.searchView)
|
||||
setupSearch()
|
||||
|
||||
}
|
||||
|
||||
private fun handleListItems() {
|
||||
|
|
@ -175,10 +229,38 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun setupRecyclerView(newApps: MutableList<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>) {
|
||||
adapter = AppMenuAdapter(this@MainActivity, newApps, this@MainActivity, this@MainActivity, this@MainActivity)
|
||||
appMenuLinearLayoutManager.stackFromEnd = true
|
||||
recyclerView = findViewById(R.id.recycler_view)
|
||||
withContext(Dispatchers.Main) {
|
||||
recyclerView.layoutManager = appMenuLinearLayoutManager
|
||||
recyclerView.edgeEffectFactory = appMenuEdgeFactory
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.scrollToPosition(0)
|
||||
}
|
||||
|
||||
setupRecyclerListener()
|
||||
}
|
||||
|
||||
private fun setupRecyclerListener() {
|
||||
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
appMenuLinearLayoutManager.setScrollInfo()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupSearch() {
|
||||
binding.root.addOnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
|
||||
|
||||
if (bottom - top > oldBottom - oldTop) {
|
||||
|
||||
searchView.clearFocus()
|
||||
|
||||
if (searchView.text.isNullOrEmpty()) {
|
||||
job?.cancel()
|
||||
startTask()
|
||||
|
|
@ -195,7 +277,9 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
filterItems(searchView.text.toString())
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
filterItems(searchView.text.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
|
|
@ -203,8 +287,8 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
})
|
||||
}
|
||||
|
||||
private fun filterItems(query: String?) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
private suspend fun filterItems(query: String?) {
|
||||
|
||||
val cleanQuery = query?.clean()
|
||||
val newFilteredApps = mutableListOf<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>()
|
||||
val updatedApps = appUtils.getInstalledApps(this@MainActivity)
|
||||
|
|
@ -212,13 +296,12 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
getFilteredApps(cleanQuery, newFilteredApps, updatedApps)
|
||||
|
||||
applySearch(newFilteredApps)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun getFilteredApps(cleanQuery: String?, newFilteredApps: MutableList<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>, updatedApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>) {
|
||||
private suspend fun getFilteredApps(cleanQuery: String?, newFilteredApps: MutableList<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>, updatedApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>) {
|
||||
if (cleanQuery.isNullOrEmpty()) {
|
||||
manualRefresh()
|
||||
refreshAppMenu()
|
||||
newFilteredApps.addAll(installedApps)
|
||||
} else {
|
||||
updatedApps.forEach {
|
||||
|
|
@ -242,83 +325,15 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
return this.replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
showHome()
|
||||
super.onNewIntent(intent)
|
||||
|
||||
}
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
job?.cancel()
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
startTask()
|
||||
|
||||
// Keyboard is sometimes open when going back to the app, so close it.
|
||||
val imm =
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(binding.root.windowToken, 0)
|
||||
}
|
||||
|
||||
private fun startTask() {
|
||||
job = CoroutineScope(Dispatchers.Default).launch {
|
||||
while (true) {
|
||||
manualRefresh()
|
||||
refreshAppMenu()
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
gestureDetector.onTouchEvent(event)
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onFling(
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float
|
||||
): Boolean {
|
||||
if (e1 != null) {
|
||||
val deltaY = e2.y - e1.y
|
||||
val deltaX = e2.x - e1.x
|
||||
|
||||
// Detect swipe up
|
||||
if (deltaY < -SWIPE_THRESHOLD && abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
|
||||
openAppMenuActivity()
|
||||
return true
|
||||
}
|
||||
|
||||
// Detect swipe left
|
||||
else if (deltaX < -SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD){
|
||||
startActivity(phoneIntent)
|
||||
}
|
||||
|
||||
// Detect swipe right
|
||||
else if (deltaX > -SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
|
||||
startActivity(cameraIntent)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
companion object {
|
||||
private const val SWIPE_THRESHOLD = 100
|
||||
private const val SWIPE_VELOCITY_THRESHOLD = 100
|
||||
}
|
||||
|
||||
private fun View.slideInFromBottom(duration: Long = 100) {
|
||||
if (visibility != View.VISIBLE) {
|
||||
translationY = height.toFloat()/5
|
||||
|
|
@ -500,7 +515,6 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
.firstOrNull()
|
||||
appActionMenu.setActionListeners(
|
||||
this@MainActivity,
|
||||
CoroutineScope(Dispatchers.Main),
|
||||
binding,
|
||||
textView,
|
||||
editView,
|
||||
|
|
@ -515,8 +529,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
)
|
||||
}
|
||||
|
||||
fun manualRefresh() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
suspend fun refreshAppMenu() {
|
||||
try {
|
||||
val updatedApps = appUtils.getInstalledApps(this@MainActivity)
|
||||
val changes = detectChanges(installedApps, updatedApps)
|
||||
|
|
@ -527,7 +540,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
|
|||
}
|
||||
catch (_: UninitializedPropertyAccessException) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun detectChanges(oldList: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>, newList: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>): List<Change> {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
package eu.ottop.yamlauncher
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.provider.MediaStore
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_view"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -10,10 +9,6 @@
|
|||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<ViewSwitcher
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/app_view"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -36,7 +31,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="Select an app"
|
||||
android:text="@string/select_an_app"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#C1F3F3F3"
|
||||
android:textSize="36sp"
|
||||
|
|
@ -51,9 +46,7 @@
|
|||
android:fadingEdgeLength="20dp"
|
||||
android:padding="0dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:stackFromEnd="true">
|
||||
android:scrollbars="none">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
|
|
@ -68,10 +61,10 @@
|
|||
android:layout_weight="0.1"
|
||||
android:background="@android:color/transparent"
|
||||
android:cursorVisible="true"
|
||||
android:drawableLeft="@android:drawable/ic_menu_search"
|
||||
android:drawableStart="@android:drawable/ic_menu_search"
|
||||
android:drawablePadding="8dp"
|
||||
android:editTextColor="#f3f3f3"
|
||||
android:hint="Search..."
|
||||
android:hint="@string/search"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textSize="25sp" />
|
||||
|
|
@ -119,7 +112,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -135,7 +128,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -151,7 +144,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -167,7 +160,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -183,7 +176,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -199,7 +192,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -215,7 +208,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
@ -231,7 +224,7 @@
|
|||
android:paddingTop="20dp"
|
||||
android:paddingRight="40dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="App"
|
||||
android:text="@string/shortcut_default"
|
||||
android:textAppearance="@android:style/TextAppearance.DeviceDefault"
|
||||
android:textColor="#F3F3F3"
|
||||
android:textSize="28sp"
|
||||
|
|
|
|||
|
|
@ -45,4 +45,8 @@
|
|||
</string>
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="select_an_app">Select an app</string>
|
||||
<string name="search">Search…</string>
|
||||
<string name="app">App</string>
|
||||
<string name="shortcut_default">App</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue