Implemented #14 and #15: favorite apps and hidden apps

This commit is contained in:
Josia Pietsch 2024-09-23 18:45:20 +02:00
parent e4b1bccf85
commit 662efd4ecb
Signed by: jrpie
GPG key ID: E70B571D66986A2D
19 changed files with 372 additions and 89 deletions

View file

@ -8,15 +8,6 @@
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">

View file

@ -41,8 +41,6 @@ android {
}
buildTypes {
release {
// minifyEnabled true

View file

@ -32,6 +32,12 @@ enum class LauncherAction(
R.drawable.baseline_menu_24,
::openAppsList
),
CHOOSE_FROM_FAVORITES(
"launcher:chooseFromFavorites",
R.string.list_other_list_favorites,
R.drawable.baseline_favorite_24,
{ context -> openAppsList(context, true)}
),
VOLUME_UP(
"launcher:volumeUp",
R.string.list_other_volume_up,
@ -174,12 +180,15 @@ private fun openSettings(context: Context) {
context.startActivity(Intent(context, SettingsActivity::class.java))
}
private fun openAppsList(context: Context) {
fun openAppsList(context: Context, favorite: Boolean = false, hidden: Boolean = false) {
val intent = Intent(context, ListActivity::class.java)
intent.putExtra("intention", ListActivity.ListActivityIntention.VIEW.toString())
intent.putExtra("favorite", favorite)
intent.putExtra("hidden", hidden)
context.startActivity(intent)
}

View file

@ -0,0 +1,55 @@
package de.jrpie.android.launcher.apps
import de.jrpie.android.launcher.preferences.LauncherPreferences
import java.util.*
import kotlin.text.Regex.Companion.escapeReplacement
class AppFilter(
var search: String,
var showOnlyFavorites: Boolean = false,
var showOnlyHidden: Boolean = false
) {
operator fun invoke(apps: List<DetailedAppInfo>): List<DetailedAppInfo> {
var apps = apps
val hidden = LauncherPreferences.apps().hidden() ?: setOf()
apps = apps.filter { info -> !showOnlyHidden.xor(hidden.contains(info.app)) }
if (showOnlyFavorites) {
val favorites = LauncherPreferences.apps().favorites() ?: setOf()
apps = apps.filter { info -> favorites.contains(info.app) }
}
// normalize text for search
var allowedSpecialCharacters = search
.lowercase(Locale.ROOT)
.toCharArray()
.distinct()
.filter { c -> !c.isLetter() }
.map { c -> escapeReplacement(c.toString()) }
.fold("") { x, y -> x + y }
var disallowedCharsRegex = "[^\\p{L}$allowedSpecialCharacters]".toRegex()
fun normalize(text: String): String {
return text.lowercase(Locale.ROOT).replace(disallowedCharsRegex, "")
}
if (search.isEmpty()) {
return apps;
} else {
val r: MutableList<DetailedAppInfo> = ArrayList()
val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
val normalizedText: String = normalize(search)
for (item in apps) {
val itemLabel: String = normalize(item.label.toString())
if (itemLabel.startsWith(normalizedText)) {
r.add(item)
} else if (itemLabel.contains(normalizedText)) {
appsSecondary.add(item)
}
}
r.addAll(appsSecondary)
return r;
}
}
}

View file

@ -17,6 +17,17 @@ class AppInfo(val packageName: CharSequence, val user: Int = INVALID_USER) {
return "$packageName;$u"
}
override fun equals(other: Any?): Boolean {
if(other is AppInfo) {
return other.user == user && other.packageName == packageName
}
return super.equals(other)
}
override fun hashCode(): Int {
return packageName.hashCode()
}
fun getLauncherActivityInfo(
context: Context
): LauncherActivityInfo? {

View file

@ -1,12 +1,18 @@
package de.jrpie.android.launcher.preferences;
import java.util.HashSet;
import java.util.Set;
import de.jrpie.android.launcher.R;
import de.jrpie.android.launcher.apps.AppInfo;
import de.jrpie.android.launcher.preferences.theme.Background;
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font;
import eu.jonahbauer.android.preference.annotations.Preference;
import eu.jonahbauer.android.preference.annotations.PreferenceGroup;
import eu.jonahbauer.android.preference.annotations.Preferences;
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException;
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer;
@Preferences(
name = "de.jrpie.android.launcher.preferences.LauncherPreferences",
@ -18,6 +24,10 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
@Preference(name = "started_time", type = long.class),
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
}),
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
@Preference(name = "favorites", type = Set.class, serializer = LauncherPreferences$Config.AppInfoSetSerializer.class),
@Preference(name = "hidden", type = Set.class, serializer = LauncherPreferences$Config.AppInfoSetSerializer.class),
}),
@PreferenceGroup(name = "gestures", prefix = "settings_gesture_", suffix = "_key", value = {
}),
@PreferenceGroup(name = "general", prefix = "settings_general_", suffix = "_key", value = {
@ -53,4 +63,32 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
}),
})
public final class LauncherPreferences$Config {
public static class AppInfoSetSerializer implements PreferenceSerializer<Set<AppInfo>, Set<String>> {
@Override
public Set<String> serialize(Set<AppInfo> value) throws PreferenceSerializationException {
if (value == null) return null;
var serialized = new HashSet<String>(value.size());
for (var app : value) {
serialized.add(app.serialize());
}
return serialized;
}
@Override
public Set<AppInfo> deserialize(Set<String> value) throws PreferenceSerializationException {
if (value == null) return null;
var deserialized = new HashSet<AppInfo>(value.size());
for (var s : value) {
deserialized.add(AppInfo.Companion.deserialize(s));
}
return deserialized;
}
}
}

View file

@ -27,6 +27,8 @@ import de.jrpie.android.launcher.ui.list.other.ListFragmentOther
// TODO: Better solution for this intercommunication functionality (used in list-fragments)
var intention = ListActivity.ListActivityIntention.VIEW
var showFavorites = false
var showHidden = false
var forGesture: String? = null
/**
@ -45,9 +47,24 @@ class ListActivity : AppCompatActivity(), UIObject {
PICK(R.string.list_title_pick) /* choose app or action to associate to a gesture */
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// get info about which action this activity is open for
intent.extras?.let { bundle ->
intention = bundle.getString("intention")
?.let { ListActivityIntention.valueOf(it) }
?: ListActivityIntention.VIEW
showFavorites = bundle.getBoolean("favorite") ?: false
showHidden = bundle.getBoolean("hidden") ?: false
if (intention != ListActivityIntention.VIEW)
forGesture = bundle.getString("forGesture")
}
// Initialise layout
binding = ListBinding.inflate(layoutInflater)
setContentView(binding.root)
@ -105,6 +122,23 @@ class ListActivity : AppCompatActivity(), UIObject {
}
}
fun updateTitle() {
var titleResource = intention.titleResource
if (intention == ListActivityIntention.VIEW) {
titleResource = if (showHidden) {
R.string.list_title_hidden
} else if (showFavorites) {
R.string.list_title_favorite
} else {
R.string.list_title_view
}
}
binding.listHeading.text = getString(titleResource)
}
override fun getTheme(): Resources.Theme {
return modifyTheme(super.getTheme())
}
@ -114,22 +148,13 @@ class ListActivity : AppCompatActivity(), UIObject {
}
override fun adjustLayout() {
// get info about which action this activity is open for
intent.extras?.let { bundle ->
intention = bundle.getString("intention")
?.let { ListActivityIntention.valueOf(it) }
?: ListActivityIntention.VIEW
if (intention != ListActivityIntention.VIEW)
forGesture = bundle.getString("forGesture")
}
// Hide tabs for the "view" action
if (intention == ListActivityIntention.VIEW) {
binding.listTabs.visibility = View.GONE
}
binding.listHeading.text = getString(intention.titleResource)
updateTitle()
val sectionsPagerAdapter = ListSectionsPagerAdapter(this, supportFragmentManager)
val viewPager: ViewPager = findViewById(R.id.list_viewpager)

View file

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Rect
import android.os.AsyncTask
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -12,9 +13,11 @@ import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.appsList
@ -24,8 +27,6 @@ import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.transformGrayscale
import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.uninstallApp
import java.util.*
import kotlin.text.Regex.Companion.escapeReplacement
/**
* A [RecyclerView] (efficient scrollable list) containing all apps on the users device.
@ -37,13 +38,16 @@ import kotlin.text.Regex.Companion.escapeReplacement
*/
class AppsRecyclerAdapter(
val activity: Activity,
val root: View,
private val intention: ListActivity.ListActivityIntention
= ListActivity.ListActivityIntention.VIEW,
private val forGesture: String? = ""
private val forGesture: String? = "",
private var appFilter: AppFilter = AppFilter("")
) :
RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() {
private val appsListDisplayed: MutableList<DetailedAppInfo>
private val appsListDisplayed: MutableList<DetailedAppInfo> = mutableListOf()
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
@ -61,6 +65,7 @@ class AppsRecyclerAdapter(
}
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val appLabel = appsListDisplayed[i].label.toString()
val appIcon = appsListDisplayed[i].icon
@ -106,6 +111,15 @@ class AppsRecyclerAdapter(
popup.menu.findItem(R.id.app_menu_delete).setVisible(false)
}
if (LauncherPreferences.apps().hidden()?.contains(appInfo.app) == true) {
popup.menu.findItem(R.id.app_menu_hidden).setTitle(R.string.list_app_hidden_remove)
}
if (LauncherPreferences.apps().favorites()?.contains(appInfo.app) == true) {
popup.menu.findItem(R.id.app_menu_favorite).setTitle(R.string.list_app_favorite_remove)
}
popup.setOnMenuItemClickListener {
when (it.itemId) {
R.id.app_menu_delete -> {
@ -118,6 +132,53 @@ class AppsRecyclerAdapter(
true
}
R.id.app_menu_favorite -> {
var favorites: MutableSet<AppInfo> =
LauncherPreferences.apps().favorites() ?: mutableSetOf()
Log.i("LAUNCHER", favorites.size.toString())
for (app in favorites) {
Log.i("LAUNCHER", app.serialize())
}
if (favorites.contains(appInfo.app)) {
favorites.remove(appInfo.app)
Log.i("LAUNCHER", "Removing " + appInfo.app.serialize() + " from favorites.")
} else {
Log.i("LAUNCHER", "Adding " + appInfo.app.serialize() + " to favorites.")
favorites.add(appInfo.app)
}
Log.i("LAUNCHER", favorites.size.toString())
for (app in favorites) {
Log.i("LAUNCHER", app.serialize())
}
LauncherPreferences.apps().favorites(favorites)
true
}
R.id.app_menu_hidden -> {
var hidden: MutableSet<AppInfo> =
LauncherPreferences.apps().hidden() ?: mutableSetOf()
if (hidden.contains(appInfo.app)) {
hidden.remove(appInfo.app)
} else {
hidden.add(appInfo.app)
Snackbar.make(root, R.string.snackbar_app_hidden, Snackbar.LENGTH_LONG)
.setAction(R.string.undo) {
LauncherPreferences.apps().hidden(
LauncherPreferences.apps().hidden().minus(appInfo.app)
)
}.show()
}
LauncherPreferences.apps().hidden(hidden)
true
}
else -> false
}
}
@ -144,9 +205,8 @@ class AppsRecyclerAdapter(
AsyncTask.execute { loadApps(activity.packageManager, activity) }
notifyDataSetChanged()
}
updateAppsList()
appsListDisplayed = ArrayList()
appsListDisplayed.addAll(appsList)
}
fun selectItem(pos: Int, rect: Rect = Rect()) {
@ -169,42 +229,13 @@ class AppsRecyclerAdapter(
}
}
/**
* The function [filter] is used to search elements within this [RecyclerView].
*/
fun filter(text: String) {
// normalize text for search
var allowedSpecialCharacters = text
.lowercase(Locale.ROOT)
.toCharArray()
.distinct()
.filter { c -> !c.isLetter() }
.map { c -> escapeReplacement(c.toString()) }
.fold("") { x, y -> x + y }
var disallowedCharsRegex = "[^\\p{L}$allowedSpecialCharacters]".toRegex()
fun normalize(text: String): String {
return text.lowercase(Locale.ROOT).replace(disallowedCharsRegex, "")
}
fun updateAppsList(triggerAutoLaunch: Boolean = false) {
appsListDisplayed.clear()
if (text.isEmpty()) {
appsListDisplayed.addAll(appsList)
} else {
val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
val normalizedText: String = normalize(text)
for (item in appsList) {
val itemLabel: String = normalize(item.label.toString())
appsListDisplayed.addAll(appFilter(appsList))
if (itemLabel.startsWith(normalizedText)) {
appsListDisplayed.add(item)
} else if (itemLabel.contains(normalizedText)) {
appsSecondary.add(item)
}
}
appsListDisplayed.addAll(appsSecondary)
}
if (appsListDisplayed.size == 1 && intention == ListActivity.ListActivityIntention.VIEW
if (triggerAutoLaunch &&
appsListDisplayed.size == 1
&& intention == ListActivity.ListActivityIntention.VIEW
&& LauncherPreferences.functionality().searchAutoLaunch()
) {
val info = appsListDisplayed[0]
@ -217,4 +248,23 @@ class AppsRecyclerAdapter(
notifyDataSetChanged()
}
/**
* The function [setSearchString] is used to search elements within this [RecyclerView].
*/
fun setSearchString(search: String) {
appFilter.search = search
updateAppsList(true)
}
fun setShowOnlyFavorites(show: Boolean) {
appFilter.showOnlyFavorites = show
updateAppsList()
}
fun setShowHiddenApps(show: Boolean) {
appFilter.showOnlyHidden = show
updateAppsList()
}
}

View file

@ -1,11 +1,13 @@
package de.jrpie.android.launcher.ui.list.apps
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.databinding.ListAppsBinding
import de.jrpie.android.launcher.openSoftKeyboard
import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -13,6 +15,8 @@ import de.jrpie.android.launcher.ui.UIObject
import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.list.forGesture
import de.jrpie.android.launcher.ui.list.intention
import de.jrpie.android.launcher.ui.list.showFavorites
import de.jrpie.android.launcher.ui.list.showHidden
/**
@ -22,6 +26,12 @@ import de.jrpie.android.launcher.ui.list.intention
*/
class ListFragmentApps : Fragment(), UIObject {
private lateinit var binding: ListAppsBinding
private lateinit var appsRViewAdapter: AppsRecyclerAdapter
private var sharedPreferencesListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
appsRViewAdapter.updateAppsList()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@ -34,13 +44,28 @@ class ListFragmentApps : Fragment(), UIObject {
override fun onStart() {
super<Fragment>.onStart()
super<UIObject>.onStart()
LauncherPreferences.getSharedPreferences()
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
binding.listAppsCheckBoxFavorites.isChecked = showFavorites
}
override fun onStop() {
super.onStop()
LauncherPreferences.getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
}
override fun setOnClicks() {}
override fun adjustLayout() {
val appsRViewAdapter = AppsRecyclerAdapter(requireActivity(), intention, forGesture)
appsRViewAdapter =
AppsRecyclerAdapter(
requireActivity(), binding.root, intention, forGesture,
appFilter = AppFilter("", showOnlyFavorites = showFavorites, showOnlyHidden = showHidden)
)
// set up the list / recycler
binding.listAppsRview.apply {
@ -54,17 +79,23 @@ class ListFragmentApps : Fragment(), UIObject {
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
appsRViewAdapter.filter(query)
appsRViewAdapter.setSearchString(query)
appsRViewAdapter.selectItem(0)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
appsRViewAdapter.filter(newText)
appsRViewAdapter.setSearchString(newText)
return false
}
})
binding.listAppsCheckBoxFavorites.setOnClickListener {
showFavorites = binding.listAppsCheckBoxFavorites.isChecked
appsRViewAdapter.setShowOnlyFavorites(showFavorites)
(activity as? ListActivity)?.updateTitle()
}
if (intention == ListActivity.ListActivityIntention.VIEW
&& LauncherPreferences.functionality().searchAutoOpenKeyboard()
) {

View file

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.openAppsList
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.setDefaultHomeScreen
@ -65,6 +66,14 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() {
setDefaultHomeScreen(requireContext(), checkDefault = false)
true
}
val hiddenApps = findPreference<androidx.preference.Preference>(
LauncherPreferences.apps().keys().hidden()
)
hiddenApps?.setOnPreferenceClickListener {
openAppsList(requireContext(), favorite = false, hidden = true)
true
}
updateVisibility()
}
}

View file

@ -1,5 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:textColor"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>

View file

@ -1,5 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?android:textColor"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/baseline_favorite_24" android:state_checked="true" />
<item android:drawable="@drawable/baseline_favorite_border_24" android:state_checked="false" />
</selector>

View file

@ -1,7 +1,6 @@
<?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/list_apps_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -17,21 +16,41 @@
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_weight="1"
android:fadeScrollbars="false"
android:fastScrollAlwaysVisible="true"
android:fastScrollEnabled="true"
android:scrollbars="vertical">
<!--android:scrollbars="vertical"-->
</androidx.recyclerview.widget.RecyclerView>
<androidx.appcompat.widget.SearchView
android:id="@+id/list_apps_searchview"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8sp"
android:layout_weight="0"
android:iconifiedByDefault="false"
app:iconifiedByDefault="false"
app:queryHint="@string/list_apps_search_hint"
app:searchHintIcon="@drawable/baseline_search_24"
app:searchIcon="@drawable/baseline_search_24" />
android:orientation="horizontal"
android:layout_margin="8dp"
>
<androidx.appcompat.widget.SearchView
android:id="@+id/list_apps_searchview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:iconifiedByDefault="false"
app:iconifiedByDefault="false"
app:queryHint="@string/list_apps_search_hint"
app:searchHintIcon="@drawable/baseline_search_24"
app:searchIcon="@drawable/baseline_search_24" />
<CheckBox
android:id="@+id/list_apps_check_box_favorites"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0"
android:layout_marginEnd="16dp"
android:button="@drawable/checkbox_favorite" />
</LinearLayout>
</LinearLayout>

View file

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tutorial_finish_container"
android:paddingLeft="32sp"

View file

@ -1,7 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/app_menu_delete"
android:title="@string/list_app_delete" />
<item android:id="@+id/app_menu_info"
android:title="@string/list_app_info" />
<item android:id="@+id/app_menu_favorite"
android:title="@string/list_app_favorite_add" />
<item android:id="@+id/app_menu_hidden"
android:title="@string/list_app_hidden_add" />
<item android:id="@+id/app_menu_delete"
android:title="@string/list_app_delete" />
</menu>

View file

@ -9,7 +9,10 @@
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
<string name="settings_general_choose_home_screen_key" translatable="false">general.select_launcher</string>
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
<string name="settings_general_choose_home_screen_key" translatable="false">general.select_launcher</string>
<!--
-
- Settings : Gestures

View file

@ -114,6 +114,9 @@
<string name="settings_launcher_sensitivity">Sensitivity</string>
<string name="settings_launcher_section_apps">Apps</string>
<string name="settings_apps_hidden">Hidden apps</string>
<!--
-
- Settings : Meta
@ -144,6 +147,8 @@
-
-->
<string name="list_title_view">All Apps</string>
<string name="list_title_favorite">Favorite Apps</string>
<string name="list_title_hidden">Hidden Apps</string>
<string name="list_title_pick">Choose App</string>
<string name="list_tab_app">Apps</string>
@ -151,6 +156,10 @@
<string name="list_app_delete">Uninstall</string>
<string name="list_app_info">App Info</string>
<string name="list_app_favorite_add">Add to favorites</string>
<string name="list_app_favorite_remove">Remove from favorites</string>
<string name="list_app_hidden_add">Hide</string>
<string name="list_app_hidden_remove">Show</string>
<string name="list_removed">Removed the selected application</string>
<string name="list_not_removed">Unable to remove application</string>
@ -159,6 +168,7 @@
<string name="list_other_settings">Launcher Settings</string>
<string name="list_other_list">All Applications</string>
<string name="list_other_list_favorites">Favorite Applications</string>
<string name="list_other_volume_up">Music: Louder</string>
<string name="list_other_volume_down">Music: Quieter</string>
<string name="list_other_track_next">Music: Next</string>
@ -195,4 +205,6 @@
<string name="settings">Settings</string>
<string name="ic_menu_alt">More options</string>
<string name="alert_cant_expand_notifications_panel">Error: Can\'t expand status bar.\nThis action is using functionality that is not part of the published Android API. Unfortunately, it does not seem to work on your device.</string>
<string name="snackbar_app_hidden">App hidden. You can make it visible again in settings.</string>
<string name="undo">Undo</string>
</resources>

View file

@ -97,6 +97,17 @@
android:title="@string/settings_enabled_gestures_edge_swipe"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/settings_launcher_section_apps"
app:allowDividerAbove="false">
<Preference
android:key="@string/settings_apps_hidden_key"
android:title="@string/settings_apps_hidden"
/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/settings_launcher_section_display"