Refactoring everything into a recyclerview. Missing: UI Design, Renaming functionality

This commit is contained in:
ottoptj 2024-05-09 11:29:01 +03:00
commit f21f13ba6c
8 changed files with 433 additions and 60 deletions

View file

@ -47,6 +47,7 @@ dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("com.google.code.gson:gson:2.10")
implementation("androidx.recyclerview:recyclerview:1.3.2")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

View file

@ -0,0 +1,118 @@
package eu.ottop.yamlauncher
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.SearchView
import android.widget.TextView
import eu.ottop.yamlauncher.databinding.ActivityAppMenuBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class AppActionMenu {
private val sharedPreferenceManager = SharedPreferenceManager()
fun setActionListeners(
activity: AppMenuActivity,
uiScope: CoroutineScope,
binding: ActivityAppMenuBinding,
textView: TextView,
editLayout: LinearLayout,
actionMenu: View,
searchView: SearchView,
appInfo: ApplicationInfo,
userHandle: UserHandle,
workProfile: Int,
launcherApps: LauncherApps,
mainActivity: LauncherActivityInfo?
){
actionMenu.findViewById<TextView>(R.id.info).setOnClickListener {
if (mainActivity != null) {
launcherApps.startAppDetailsActivity(
mainActivity.componentName,
userHandle,
null,
null
)
}
actionMenu.visibility = View.GONE
textView.visibility = View.VISIBLE
}
actionMenu.findViewById<TextView>(R.id.uninstall).setOnClickListener {
val intent = Intent(Intent.ACTION_DELETE)
intent.data = Uri.parse("package:${appInfo.packageName}")
intent.putExtra(Intent.EXTRA_USER, userHandle)
activity.startActivity(intent)
actionMenu.visibility = View.GONE
textView.visibility = View.VISIBLE
}
actionMenu.findViewById<TextView>(R.id.rename).setOnClickListener {
textView.visibility = View.GONE
editLayout.visibility = View.VISIBLE
actionMenu.visibility = View.GONE
val editText = editLayout.findViewById<EditText>(R.id.app_name)
searchView.visibility = View.GONE
editText.requestFocus()
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
//binding.appList.scrollToDescendant(textView)
}, 100)
binding.root.addOnLayoutChangeListener {
_, _, top, _, bottom, _, oldTop, _, oldBottom ->
if (bottom - top > oldBottom - oldTop) {
editLayout.clearFocus()
editLayout.visibility = View.GONE
textView.visibility = View.VISIBLE
searchView.visibility = View.VISIBLE
}
}
editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(editText.windowToken, 0)
sharedPreferenceManager.setAppName(activity, appInfo.packageName, workProfile, editText.text.toString())
uiScope.launch {
//activity.refreshAppMenu()
}
return@setOnEditorActionListener true
}
false
}
}
actionMenu.findViewById<TextView>(R.id.hide).setOnClickListener {
sharedPreferenceManager.setAppHidden(activity, appInfo.packageName, workProfile, true)
actionMenu.visibility = View.GONE
textView.visibility = View.GONE
}
actionMenu.findViewById<TextView>(R.id.close).setOnClickListener {
actionMenu.visibility = View.GONE
textView.visibility = View.VISIBLE
}
}
}

View file

@ -1,41 +1,208 @@
package eu.ottop.yamlauncher
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.webkit.RenderProcessGoneDetail
import android.widget.EditText
import android.widget.ImageView
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.SearchView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.toDrawable
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
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() {
class AppMenuActivity : AppCompatActivity(), AppMenuAdapter.OnItemClickListener, AppMenuAdapter.OnItemLongClickListener {
private lateinit var binding: ActivityAppMenuBinding
private lateinit var recyclerView: RecyclerView
private lateinit var searchView: SearchView
private lateinit var adapter: AppMenuAdapter
private lateinit var shownApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>
private lateinit var job: Job
private var appActionMenu = AppActionMenu()
private lateinit var launcherApps: LauncherApps
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAppMenuBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(null)
launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
searchView = findViewById(R.id.searchView)
recyclerView = findViewById(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
shownApps = getInstalledApps()
adapter = AppMenuAdapter(shownApps, this, this)
recyclerView.adapter = adapter
}
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 {
// Handle the case when launch intent is null (e.g., app cannot be launched)
Toast.makeText(this, "Cannot launch app", Toast.LENGTH_SHORT).show()
}
}
override fun onItemLongClick(
appInfo: LauncherActivityInfo,
userHandle: UserHandle,
userProfile: Int,
textView: TextView,
actionMenuLayout: LinearLayout,
editView: LinearLayout
) {
// Handle the long click action here, for example, show additional options or information about the app
textView.visibility = View.GONE
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)
}
private fun getInstalledApps(): List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>> {
val allApps = mutableListOf<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>()
val launcherApps = getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps
for (i in launcherApps.profiles.indices) {
launcherApps.getActivityList(null, launcherApps.profiles[i]).forEach { app ->
allApps.add(Pair(app, Pair(launcherApps.profiles[i], i)))
}
}
return allApps.sortedBy {
it.first.applicationInfo.loadLabel(packageManager).toString().lowercase()
}
}
override fun onStop() {
super.onStop()
job.cancel()
}
override fun onStart() {
super.onStart()
startTask()
}
private fun startTask() {
job = CoroutineScope(Dispatchers.IO).launch {
while (true) {
if (!listsEqual(shownApps, getInstalledApps())) {
shownApps = getInstalledApps()
withContext(Dispatchers.Main) {
adapter.updateApps(shownApps)
}
}
delay(5000)
}
}
}
private fun listsEqual(
list1: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>,
list2: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>
): Boolean {
if (list1.size != list2.size) return false
for (i in list1.indices) {
if (list1[i].first.componentName != list2[i].first.componentName || list1[i].second.first != list2[i].second.first) {
return false
}
}
return true
}
}
class AppMenuAdapter(private var apps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>, private val itemClickListener: OnItemClickListener, private val itemLongClickListener: OnItemLongClickListener) :
RecyclerView.Adapter<AppMenuAdapter.AppViewHolder>() {
interface OnItemClickListener {
fun onItemClick(appInfo: LauncherActivityInfo, userHandle: UserHandle)
}
interface OnItemLongClickListener {
fun onItemLongClick(
appInfo: LauncherActivityInfo,
userHandle: UserHandle,
userProfile: Int,
textView: TextView,
actionMenuLayout: LinearLayout,
editView: LinearLayout
)
}
inner class AppViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.app_name)
init {
val actionMenuLayout = LayoutInflater.from(itemView.context)
.inflate(R.layout.app_action_menu, itemView.findViewById(R.id.list_item), false) as LinearLayout
val editView = LayoutInflater.from(itemView.context)
.inflate(R.layout.rename_view, itemView.findViewById(R.id.list_item), false) as LinearLayout
val parentLayout: ViewGroup = itemView.findViewById(R.id.list_item)
parentLayout.addView(actionMenuLayout)
parentLayout.addView(editView)
actionMenuLayout.visibility = View.GONE
editView.visibility = View.GONE
itemView.setOnClickListener {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val app = apps[position].first
itemClickListener.onItemClick(app, apps[position].second.first)
}
}
itemView.setOnLongClickListener {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val app = apps[position].first
itemLongClickListener.onItemLongClick(app, apps[position].second.first, apps[position].second.second, textView, actionMenuLayout, editView)
return@setOnLongClickListener true
}
false
}
}
}
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]
val appInfo = app.first.activityInfo.applicationInfo
holder.textView.text = appInfo.loadLabel(holder.itemView.context.packageManager)
}
override fun getItemCount(): Int {
return apps.size
}
fun updateApps(newApps: List<Pair<LauncherActivityInfo, Pair<UserHandle, Int>>>) {
apps = newApps
notifyDataSetChanged()
}
}
/*
private lateinit var binding: ActivityAppMenuBinding
private lateinit var searchView: SearchView
private lateinit var container: LinearLayout
@ -415,4 +582,4 @@ class AppMenuActivity : AppCompatActivity() {
val key = "$packageName-$profile"
return sharedPreferences.getString(key, appName.toString())
}
}
}*/

View file

@ -0,0 +1,48 @@
package eu.ottop.yamlauncher
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
class SharedPreferenceManager {
fun setAppHidden(cont: Context, packageName: String, profile: Int, hidden: Boolean) {
val editor = cont.getSharedPreferences("hidden_apps", AppCompatActivity.MODE_PRIVATE).edit()
val key = "$packageName-$profile"
editor.putBoolean(key, hidden)
editor.apply()
}
fun isAppHidden(cont: Context, packageName: String, profile: Int): Boolean {
val sharedPref = cont.getSharedPreferences("hidden_apps", AppCompatActivity.MODE_PRIVATE)
val key = "$packageName-$profile"
return sharedPref.getBoolean(key, false) // Default to false (visible)
}
fun setAppVisible(cont: Context, packageName: String, profile: Int) {
val editor = cont.getSharedPreferences("hidden_apps", AppCompatActivity.MODE_PRIVATE).edit()
val key = "$packageName-$profile"
editor.remove(key)
editor.apply()
}
fun setAppName(cont: Context, packageName: String, profile: Int, newName: String) {
val editor = cont.getSharedPreferences("renamed_apps", AppCompatActivity.MODE_PRIVATE).edit()
val key = "$packageName-$profile"
editor.putString(key, newName)
editor.apply()
}
fun getAppName(cont: Context, packageName: String, profile: Int, appName: CharSequence): CharSequence? {
val sharedPreferences = cont.getSharedPreferences("renamed_apps", AppCompatActivity.MODE_PRIVATE)
val key = "$packageName-$profile"
return sharedPreferences.getString(key, appName.toString())
}
fun resetAppName(cont: Context, packageName: String, profile: Int) {
val editor = cont.getSharedPreferences("renamed_apps", AppCompatActivity.MODE_PRIVATE).edit()
val key = "$packageName-$profile"
editor.remove(key)
editor.apply()
}
}

View file

@ -1,39 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/app_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:fillViewport="true"
android:fitsSystemWindows="true"
android:gravity="bottom"
android:orientation="vertical"
tools:context=".MainActivity">
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:id="@+id/app_list"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="25dp"
android:layout_weight="0.9"
android:fadingEdge="none"
android:fillViewport="true"
android:requiresFadingEdge="vertical"
android:scrollbars="none">
android:layout_weight="1"
android:clipToPadding="false"
android:padding="0dp"
android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="none"
android:orientation="vertical"
android:padding="16dp"/>
</ScrollView>
</androidx.recyclerview.widget.RecyclerView>
<SearchView
android:id="@+id/searchView"
@ -51,4 +33,5 @@
android:queryHint="Search..."
android:theme="@style/AppSearchView" />
</LinearLayout>
</LinearLayout>

View file

@ -1,51 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/action_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#6F000000">
android:gravity="center">
<TextView
android:id="@+id/info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@android:drawable/ic_menu_info_details"
android:background="#6F000000"
android:foreground="@drawable/app_action_foreground"
android:text="Info"
android:textAlignment="center"
android:textColor="#F3F3F3FF" />
android:textColor="#F3F3F3FF"
app:drawableTopCompat="@android:drawable/ic_menu_info_details" />
<TextView
android:id="@+id/uninstall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@android:drawable/ic_menu_delete"
android:background="#6F000000"
android:foreground="@drawable/app_action_foreground"
android:text="Uninstall"
android:textAlignment="center"
android:textColor="#F3F3F3FF" />
android:textColor="#F3F3F3FF"
app:drawableTopCompat="@android:drawable/ic_menu_delete" />
<TextView
android:id="@+id/rename"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@android:drawable/ic_menu_edit"
android:background="#6F000000"
android:foreground="@drawable/app_action_foreground"
android:text="Rename"
android:textAlignment="center"
android:textColor="#F3F3F3FF" />
android:textColor="#F3F3F3FF"
app:drawableTopCompat="@android:drawable/ic_menu_edit" />
<TextView
android:id="@+id/hide"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@android:drawable/ic_menu_view"
android:background="#6F000000"
android:foreground="@drawable/app_action_foreground"
android:text="Hide"
android:textAlignment="center"
android:textColor="#F3F3F3FF" />
android:textColor="#F3F3F3FF"
app:drawableTopCompat="@android:drawable/ic_menu_view" />
<TextView
android:id="@+id/close"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#6F000000"
android:foreground="@drawable/app_action_foreground"
android:text="Close"
android:textAlignment="center"
android:textColor="#F3F3F3FF"
app:drawableTopCompat="@android:drawable/ic_menu_close_clear_cancel" />
</LinearLayout>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="0dp">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#F3F3F3"
android:textSize="28sp"
android:visibility="visible" />
</LinearLayout>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rename_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/app_name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:hint="hint" />
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:text="Reset" />
</LinearLayout>