From b12d076208721f63e2595a96dea17647c1b3a61f Mon Sep 17 00:00:00 2001 From: ottoptj Date: Mon, 27 May 2024 17:17:58 +0300 Subject: [PATCH] Home screen side swipe app launching added. App menu closing implemented. --- app/src/main/AndroidManifest.xml | 2 + .../ottop/yamlauncher/AppMenuEdgeFactory.kt | 31 +++ .../yamlauncher/AppMenuLinearLayoutManager.kt | 23 +++ .../java/eu/ottop/yamlauncher/MainActivity.kt | 176 ++++++++++++------ .../yamlauncher/SharedPreferenceManager.kt | 4 + app/src/main/res/layout/activity_main.xml | 1 - 6 files changed, 175 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/eu/ottop/yamlauncher/AppMenuEdgeFactory.kt create mode 100644 app/src/main/java/eu/ottop/yamlauncher/AppMenuLinearLayoutManager.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index afe4336..e9b802d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,6 +38,8 @@ + + diff --git a/app/src/main/java/eu/ottop/yamlauncher/AppMenuEdgeFactory.kt b/app/src/main/java/eu/ottop/yamlauncher/AppMenuEdgeFactory.kt new file mode 100644 index 0000000..f927570 --- /dev/null +++ b/app/src/main/java/eu/ottop/yamlauncher/AppMenuEdgeFactory.kt @@ -0,0 +1,31 @@ +package eu.ottop.yamlauncher + +import android.widget.EdgeEffect +import androidx.recyclerview.widget.RecyclerView + +class AppMenuEdgeFactory(private val activity: MainActivity) : RecyclerView.EdgeEffectFactory() { + + override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect { + return AppMenuEdgeEffect(activity) + } +} +class AppMenuEdgeEffect(activity: MainActivity) : EdgeEffect(activity) { + // Adjust the speed here + private val animationSpeedFactor = 0.5f + + override fun onAbsorb(velocity: Int) { + super.onAbsorb((velocity * animationSpeedFactor).toInt()) + } + + override fun onPull(deltaDistance: Float, displacement: Float) { + super.onPull(deltaDistance * animationSpeedFactor, displacement) + } + + override fun onRelease() { + super.onRelease() + } + + override fun onPullDistance(deltaDistance: Float, displacement: Float): Float { + return super.onPullDistance(deltaDistance * animationSpeedFactor, displacement) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/ottop/yamlauncher/AppMenuLinearLayoutManager.kt b/app/src/main/java/eu/ottop/yamlauncher/AppMenuLinearLayoutManager.kt new file mode 100644 index 0000000..5d58c95 --- /dev/null +++ b/app/src/main/java/eu/ottop/yamlauncher/AppMenuLinearLayoutManager.kt @@ -0,0 +1,23 @@ +package eu.ottop.yamlauncher + +import android.content.Context +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +class AppMenuLinearLayoutManager(private val activity: MainActivity) : LinearLayoutManager(activity) { + + private var scrollState = RecyclerView.SCROLL_STATE_IDLE + fun setScrollState(state: Int) { + scrollState = state + } + + 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 && scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { + activity.showHome() + } + return scrollRange + } + + } \ No newline at end of file diff --git a/app/src/main/java/eu/ottop/yamlauncher/MainActivity.kt b/app/src/main/java/eu/ottop/yamlauncher/MainActivity.kt index 9f8504b..f1bc512 100644 --- a/app/src/main/java/eu/ottop/yamlauncher/MainActivity.kt +++ b/app/src/main/java/eu/ottop/yamlauncher/MainActivity.kt @@ -4,16 +4,16 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ArgbEvaluator import android.animation.ObjectAnimator -import android.content.BroadcastReceiver +import android.annotation.SuppressLint import android.content.Context import android.content.Intent -import android.content.IntentFilter import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherApps import android.os.Bundle import android.os.Handler import android.os.Looper import android.os.UserHandle +import android.provider.MediaStore import android.text.Editable import android.text.TextWatcher import android.util.Log @@ -32,6 +32,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.view.children import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.OnScrollListener import eu.ottop.yamlauncher.databinding.ActivityMainBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -52,10 +53,14 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap private lateinit var searchView: EditText private lateinit var adapter: AppMenuAdapter private var job: Job? = null - private var appActionMenu = AppActionMenu() + val cameraIntent = Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) + val phoneIntent = Intent(Intent.ACTION_DIAL) + private var appActionMenu = AppActionMenu() private val sharedPreferenceManager = SharedPreferenceManager() private val appUtils = AppUtils() + private val appMenuLinearLayoutManager = AppMenuLinearLayoutManager(this@MainActivity) + private val appMenuEdgeFactory = AppMenuEdgeFactory(this@MainActivity) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -65,19 +70,70 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + gestureDetector = GestureDetector(this, GestureListener()) + + setupApps() + + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + showHome() + + } + }) + + } + + private fun setupApps() { + handleListItems() + + CoroutineScope(Dispatchers.Default).launch { + installedApps = appUtils.getInstalledApps(this@MainActivity) + + val newApps = mutableListOf>>() + newApps.addAll(installedApps) + + setupRecyclerView(newApps) + + searchView = findViewById(R.id.searchView) + setupSearch() + } + } + + private suspend fun setupRecyclerView(newApps: MutableList>>) { + adapter = AppMenuAdapter(this@MainActivity, newApps, this@MainActivity, this@MainActivity, this@MainActivity) + withContext(Dispatchers.Main) { + recyclerView = findViewById(R.id.recycler_view) + recyclerView.layoutManager = appMenuLinearLayoutManager + recyclerView.edgeEffectFactory = appMenuEdgeFactory + recyclerView.adapter = adapter + recyclerView.scrollToPosition(0) + } + + recyclerView.addOnScrollListener(object: OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + appMenuLinearLayoutManager.setScrollState(newState) + } + }) + } + + @SuppressLint("ClickableViewAccessibility") + private fun handleListItems() { for (i in findViewById(R.id.shortcuts).children) { val textView = i as TextView + textView.setOnTouchListener() {_, event -> + gestureDetector.onTouchEvent(event) + super.onTouchEvent(event) + } + val savedView = sharedPreferenceManager.getShortcut(this, textView) textView.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(resources, R.drawable.ic_empty, null),null,null,null) textView.compoundDrawablePadding = 0 - - i.setOnClickListener { - Toast.makeText(this, "Long click to select an app", Toast.LENGTH_SHORT).show() - } + unselectedListeners(textView) if (savedView?.get(1) != "e") { @@ -88,56 +144,35 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap textView.setCompoundDrawablesWithIntrinsicBounds(ResourcesCompat.getDrawable(resources, R.drawable.ic_empty, null),null,null,null) } textView.text = savedView?.get(2) - textView.setOnClickListener { - val mainActivity = launcherApps.getActivityList(savedView?.get(0).toString(), launcherApps.profiles[savedView?.get(1)!!.toInt()]).firstOrNull() - if (mainActivity != null) { - launcherApps.startMainActivity(mainActivity.componentName, launcherApps.profiles[savedView[1].toInt()], null, null) - } else { - Toast.makeText(this, "Cannot launch app", Toast.LENGTH_SHORT).show() - } - } + selectedListeners(textView, savedView) } - i.setOnLongClickListener { - binding.homeView.FadeOut() - adapter.menuMode = "shortcut" - adapter.shortcutTextView = i - binding.appView.slideInFromBottom() - - - return@setOnLongClickListener true - }} - - - gestureDetector = GestureDetector(this, GestureListener()) - - - //Experimental - CoroutineScope(Dispatchers.Default).launch { - installedApps = appUtils.getInstalledApps(this@MainActivity) - - recyclerView = findViewById(R.id.recycler_view) - val newApps = mutableListOf>>() - newApps.addAll(installedApps) - adapter = AppMenuAdapter(this@MainActivity, newApps, this@MainActivity, this@MainActivity, this@MainActivity) - withContext(Dispatchers.Main) { - recyclerView.adapter = adapter - recyclerView.scrollToPosition(0) - } - - searchView = findViewById(R.id.searchView) - setupSearch() } + } - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - binding.appView.slideOutToBottom() - binding.homeView.FadeIn() + private fun unselectedListeners(textView: TextView) { + textView.setOnClickListener { + Toast.makeText(this, "Long click to select an app", Toast.LENGTH_SHORT).show() + } + textView.setOnLongClickListener { + adapter.menuMode = "shortcut" + adapter.shortcutTextView = textView + showApps() + + return@setOnLongClickListener true + } + } + + private fun selectedListeners(textView: TextView, savedView: List?) { + textView.setOnClickListener { + val mainActivity = launcherApps.getActivityList(savedView?.get(0).toString(), launcherApps.profiles[savedView?.get(1)!!.toInt()]).firstOrNull() + if (mainActivity != null) { + launcherApps.startMainActivity(mainActivity.componentName, launcherApps.profiles[savedView[1].toInt()], null, null) + } else { + Toast.makeText(this, "Cannot launch app", Toast.LENGTH_SHORT).show() } - }) - - + } } private fun setupSearch() { @@ -201,8 +236,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap } override fun onNewIntent(intent: Intent?) { - binding.appView.slideOutToBottom() - binding.homeView.FadeIn() + showHome() super.onNewIntent(intent) } @@ -249,13 +283,25 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap velocityX: Float, velocityY: Float ): Boolean { - // Detect swipe up gesture 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 } @@ -307,7 +353,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap } - fun View.slideOutToBottom(duration: Long = 50) { + private fun View.slideOutToBottom(duration: Long = 50) { if (visibility == View.VISIBLE) { animate() .translationY(height.toFloat() / 5) @@ -327,7 +373,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap } } - private fun View.FadeIn(duration: Long = 100) { + private fun View.fadeIn(duration: Long = 100) { if (visibility != View.VISIBLE) { alpha = 0f translationY = -height.toFloat()/100 @@ -365,7 +411,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap } } - fun View.FadeOut(duration: Long = 50) { + private fun View.fadeOut(duration: Long = 50) { if (visibility == View.VISIBLE) { animate() .alpha(0f) @@ -378,11 +424,20 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap })} } + fun showHome() { + binding.appView.slideOutToBottom() + binding.homeView.fadeIn() + } + + private fun showApps() { + binding.homeView.fadeOut() + binding.appView.slideInFromBottom() + } fun openAppMenuActivity() { //AppMenuActivity.start(this, installedApps) { //} - binding.homeView.FadeOut() - binding.appView.slideInFromBottom() + adapter.menuMode = "app" + showApps() } @@ -418,8 +473,7 @@ class MainActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, Ap } } sharedPreferenceManager.setShortcut(this, shortcutView, appInfo.applicationInfo.packageName, userProfile) - binding.appView.slideOutToBottom() - binding.homeView.slideInFromBottom() + showHome() } diff --git a/app/src/main/java/eu/ottop/yamlauncher/SharedPreferenceManager.kt b/app/src/main/java/eu/ottop/yamlauncher/SharedPreferenceManager.kt index bb8d5a5..549c713 100644 --- a/app/src/main/java/eu/ottop/yamlauncher/SharedPreferenceManager.kt +++ b/app/src/main/java/eu/ottop/yamlauncher/SharedPreferenceManager.kt @@ -1,6 +1,10 @@ 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 diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 49b81a0..a1b9344 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -48,7 +48,6 @@ android:padding="0dp" android:requiresFadingEdge="vertical" android:scrollbars="none" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:stackFromEnd="true">