Fixed returning from app menu and refactoring

This commit is contained in:
ottoptj 2024-05-28 21:02:58 +03:00
commit 5cdd7bca2e
8 changed files with 183 additions and 516 deletions

View file

@ -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()
)
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
)
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
CoroutineScope(Dispatchers.Default).launch {
sharedPreferenceManager.setAppHidden(activity, appInfo.packageName, workProfile, true)
activity.manualRefresh()
activity.refreshAppMenu()
}
}
actionMenu.findViewById<TextView>(R.id.close).setOnClickListener {

View file

@ -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")
}
}

View file

@ -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
}
}
}
}

View file

@ -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
}
}

View file

@ -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()
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)
}
}
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,16 +277,18 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
CoroutineScope(Dispatchers.Default).launch {
filterItems(searchView.text.toString())
}
}
override fun afterTextChanged(s: Editable?) {
}
})
}
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> {

View file

@ -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

View file

@ -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"

View file

@ -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>