mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	cherrypick drag and drop sorting
This commit is contained in:
		| @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.CategoryImpl | ||||
| import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS | ||||
| import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID | ||||
| import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER | ||||
| import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME | ||||
| import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER | ||||
| import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE | ||||
| @@ -40,6 +41,9 @@ class CategoryPutResolver : DefaultPutResolver<Category>() { | ||||
|         put(COL_NAME, obj.name) | ||||
|         put(COL_ORDER, obj.order) | ||||
|         put(COL_FLAGS, obj.flags) | ||||
|         val orderString = obj.mangaOrder.joinToString("/") | ||||
|         put(COL_MANGA_ORDER, orderString) | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -50,6 +54,9 @@ class CategoryGetResolver : DefaultGetResolver<Category>() { | ||||
|         name = cursor.getString(cursor.getColumnIndex(COL_NAME)) | ||||
|         order = cursor.getInt(cursor.getColumnIndex(COL_ORDER)) | ||||
|         flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS)) | ||||
|  | ||||
|         val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER)) | ||||
|         mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList() | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,8 @@ interface Category : Serializable { | ||||
|  | ||||
|     var flags: Int | ||||
|  | ||||
|     var mangaOrder:List<Long> | ||||
|  | ||||
|     val nameLower: String | ||||
|         get() = name.toLowerCase() | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,8 @@ class CategoryImpl : Category { | ||||
|  | ||||
|     override var flags: Int = 0 | ||||
|  | ||||
|     override var mangaOrder: List<Long> = emptyList() | ||||
|  | ||||
|     override fun equals(other: Any?): Boolean { | ||||
|         if (this === other) return true | ||||
|         if (other == null || javaClass != other.javaClass) return false | ||||
|   | ||||
| @@ -12,12 +12,19 @@ object CategoryTable { | ||||
|  | ||||
|     const val COL_FLAGS = "flags" | ||||
|  | ||||
|     const val COL_MANGA_ORDER = "manga_order" | ||||
|  | ||||
|     val createTableQuery: String | ||||
|         get() = | ||||
|             """CREATE TABLE $TABLE( | ||||
|             $COL_ID INTEGER NOT NULL PRIMARY KEY, | ||||
|             $COL_NAME TEXT NOT NULL, | ||||
|             $COL_ORDER INTEGER NOT NULL, | ||||
|             $COL_FLAGS INTEGER NOT NULL | ||||
|             $COL_FLAGS INTEGER NOT NULL, | ||||
|             $COL_MANGA_ORDER TEXT NOT NULL | ||||
|             )""" | ||||
|  | ||||
|  | ||||
|     val addMangaOrder: String | ||||
|         get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT" | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,10 @@ import kotlinx.coroutines.flow.filter | ||||
| import kotlinx.coroutines.flow.toList | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.davidea.flexibleadapter.SelectableAdapter | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
| import eu.kanade.tachiyomi.ui.category.CategoryAdapter | ||||
|  | ||||
| /** | ||||
|  * Adapter storing a list of manga in a certain category. | ||||
| @@ -40,6 +44,8 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) : | ||||
|      */ | ||||
|     private var mangas: List<LibraryItem> = emptyList() | ||||
|  | ||||
|     val onItemReleaseListener: CategoryAdapter.OnItemReleaseListener = view | ||||
|  | ||||
|     /** | ||||
|      * Sets a list of manga in the adapter. | ||||
|      * | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.davidea.flexibleadapter.SelectableAdapter | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Category | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.library.LibraryUpdateService | ||||
| @@ -35,7 +36,8 @@ import uy.kohesive.injekt.injectLazy | ||||
| class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : | ||||
|     FrameLayout(context, attrs), | ||||
|     FlexibleAdapter.OnItemClickListener, | ||||
|     FlexibleAdapter.OnItemLongClickListener { | ||||
|     FlexibleAdapter.OnItemLongClickListener, | ||||
|     FlexibleAdapter.OnItemMoveListener, { | ||||
|  | ||||
|     private val scope = CoroutineScope(Job() + Dispatchers.Main) | ||||
|  | ||||
| @@ -129,6 +131,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|         } else { | ||||
|             SelectableAdapter.Mode.SINGLE | ||||
|         } | ||||
|         val sortingMode = preferences.librarySortingMode().getOrDefault() | ||||
|         adapter.isLongPressDragEnabled = sortingMode == LibrarySort.DRAG_AND_DROP | ||||
|  | ||||
|         // EXH --> | ||||
|         scope = newScope() | ||||
| @@ -190,6 +194,27 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|                 } | ||||
|                 controller.invalidateActionMode() | ||||
|             } | ||||
|  | ||||
|         subscriptions += controller.reorganizeRelay | ||||
|             .subscribe { | ||||
|                 if (it.first == category.id) { | ||||
|                     var items =  when (it.second) { | ||||
|                             1, 2 -> adapter.currentItems.sortedBy { | ||||
|                                 if (preferences.removeArticles().getOrDefault()) | ||||
|                                     it.manga.title.removeArticles() | ||||
|                                 else | ||||
|                                     it.manga.title | ||||
|                             } | ||||
|                             3, 4 -> adapter.currentItems.sortedBy { it.manga.last_update } | ||||
|                             else ->  adapter.currentItems.sortedBy { it.manga.title } | ||||
|                     } | ||||
|                     if (it.second % 2 == 0) | ||||
|                         items = items.reversed() | ||||
|                     adapter.setItems(items) | ||||
|                     adapter.notifyDataSetChanged() | ||||
|                     onItemReleased(0) | ||||
|                 } | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     fun onRecycle() { | ||||
| @@ -214,8 +239,18 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|      */ | ||||
|     suspend fun onNextLibraryManga(cScope: CoroutineScope, event: LibraryMangaEvent) { | ||||
|         // Get the manga list for this category. | ||||
|         val mangaForCategory = event.getMangaForCategory(category).orEmpty() | ||||
|  | ||||
|  | ||||
|         val sortingMode = preferences.librarySortingMode().getOrDefault() | ||||
|         adapter.isLongPressDragEnabled = sortingMode == LibrarySort.DRAG_AND_DROP | ||||
|         var mangaForCategory = event.getMangaForCategory(category).orEmpty() | ||||
|         if (sortingMode == LibrarySort.DRAG_AND_DROP) { | ||||
|             if (category.name == "Default") | ||||
|                 category.mangaOrder = preferences.defaultMangaOrder().getOrDefault().split("/") | ||||
|                     .mapNotNull { it.toLongOrNull() } | ||||
|             mangaForCategory = mangaForCategory.sortedBy { category.mangaOrder.indexOf(it.manga | ||||
|                 .id) } | ||||
|         } | ||||
|         // Update the category with its manga. | ||||
|         // EXH --> | ||||
|         adapter.setItems(cScope, mangaForCategory) | ||||
| @@ -243,6 +278,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|             is LibrarySelectionEvent.Selected -> { | ||||
|                 if (adapter.mode != SelectableAdapter.Mode.MULTI) { | ||||
|                     adapter.mode = SelectableAdapter.Mode.MULTI | ||||
|                     adapter.isLongPressDragEnabled = false | ||||
|                 } | ||||
|                 findAndToggleSelection(event.manga) | ||||
|             } | ||||
| @@ -251,12 +287,16 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|                 if (adapter.indexOf(event.manga) != -1) lastClickPosition = -1 | ||||
|                 if (controller.selectedMangas.isEmpty()) { | ||||
|                     adapter.mode = SelectableAdapter.Mode.SINGLE | ||||
|                     adapter.isLongPressDragEnabled = preferences.librarySortingMode() | ||||
|                         .getOrDefault() == LibrarySort.DRAG_AND_DROP | ||||
|                 } | ||||
|             } | ||||
|             is LibrarySelectionEvent.Cleared -> { | ||||
|                 adapter.mode = SelectableAdapter.Mode.SINGLE | ||||
|                 adapter.clearSelection() | ||||
|                 lastClickPosition = -1 | ||||
|                 adapter.isLongPressDragEnabled = preferences.librarySortingMode() | ||||
|                     .getOrDefault() == LibrarySort.DRAG_AND_DROP | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -300,6 +340,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|      */ | ||||
|     override fun onItemLongClick(position: Int) { | ||||
|         controller.createActionModeIfNeeded() | ||||
|         adapter.isLongPressDragEnabled = false | ||||
|         when { | ||||
|             lastClickPosition == -1 -> setSelection(position) | ||||
|             lastClickPosition > position -> | ||||
| @@ -313,6 +354,36 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att | ||||
|         lastClickPosition = position | ||||
|     } | ||||
|  | ||||
|     override fun onItemMove(fromPosition: Int, toPosition: Int) { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     override fun onItemReleased(position: Int) { | ||||
|         if (adapter.selectedItemCount == 0) { | ||||
|             val mangaIds = adapter.currentItems.mapNotNull { it.manga.id } | ||||
|             category.mangaOrder = mangaIds | ||||
|             val db: DatabaseHelper by injectLazy() | ||||
|             if (category.name == "Default") | ||||
|                 preferences.defaultMangaOrder().set(mangaIds.joinToString("/")) | ||||
|             else | ||||
|                 db.insertCategory(category).asRxObservable().subscribe() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { | ||||
|         if (adapter.selectedItemCount > 1) | ||||
|             return false | ||||
|         if (adapter.isSelected(fromPosition)) | ||||
|             toggleSelection(fromPosition) | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { | ||||
|         val position = viewHolder?.adapterPosition ?: return | ||||
|         if (actionState == 2) | ||||
|             onItemLongClick(position) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Opens a manga. | ||||
|      * | ||||
|   | ||||
| @@ -106,6 +106,11 @@ class LibraryController( | ||||
|      */ | ||||
|     val selectInverseRelay: PublishRelay<Int> = PublishRelay.create() | ||||
|  | ||||
|     /** | ||||
|      * Relay to notify the library's viewpager to reotagnize all | ||||
|      */ | ||||
|     val reorganizeRelay: PublishRelay<Pair<Int, Int>> = PublishRelay.create() | ||||
|  | ||||
|     /** | ||||
|      * Number of manga per row in grid mode. | ||||
|      */ | ||||
| @@ -302,6 +307,7 @@ class LibraryController( | ||||
|      * Called when the sorting mode is changed. | ||||
|      */ | ||||
|     private fun onSortChanged() { | ||||
|         activity?.invalidateOptionsMenu() | ||||
|         presenter.requestSortUpdate() | ||||
|     } | ||||
|  | ||||
| @@ -343,6 +349,9 @@ class LibraryController( | ||||
|     override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { | ||||
|         inflater.inflate(R.menu.library, menu) | ||||
|  | ||||
|         val reorganizeItem = menu.findItem(R.id.action_reorganize) | ||||
|         reorganizeItem.isVisible = preferences.librarySortingMode().getOrDefault() == LibrarySort.DRAG_AND_DROP | ||||
|  | ||||
|         val searchItem = menu.findItem(R.id.action_search) | ||||
|         val searchView = searchItem.actionView as SearchView | ||||
|         searchView.maxWidth = Int.MAX_VALUE | ||||
| @@ -409,11 +418,21 @@ class LibraryController( | ||||
|                     presenter.favoritesSync.runSync() | ||||
|             } | ||||
|             // <-- EXH | ||||
| 			R.id.action_alpha_asc -> reOrder(1) | ||||
|             R.id.action_alpha_dsc -> reOrder(2) | ||||
|             R.id.action_update_asc -> reOrder(3) | ||||
|             R.id.action_update_dsc -> reOrder(4) | ||||
|         } | ||||
|  | ||||
|         return super.onOptionsItemSelected(item) | ||||
|     } | ||||
|  | ||||
|     private fun reOrder(type: Int) { | ||||
|         adapter?.categories?.getOrNull(library_pager.currentItem)?.id?.let { | ||||
|             reorganizeRelay.call(it to type) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Invalidates the action mode, forcing it to refresh its content. | ||||
|      */ | ||||
|   | ||||
| @@ -24,7 +24,7 @@ import kotlinx.android.synthetic.main.source_grid_item.unread_text | ||||
|  */ | ||||
| class LibraryGridHolder( | ||||
|     private val view: View, | ||||
|     private val adapter: FlexibleAdapter<*> | ||||
|     adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> | ||||
| ) : LibraryHolder(view, adapter) { | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder | ||||
|  | ||||
| abstract class LibraryHolder( | ||||
|     view: View, | ||||
|     adapter: FlexibleAdapter<*> | ||||
|     val adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> | ||||
| ) : BaseFlexibleViewHolder(view, adapter) { | ||||
|  | ||||
|     /** | ||||
| @@ -23,4 +23,16 @@ abstract class LibraryHolder( | ||||
|      * @param item the manga item to bind. | ||||
|      */ | ||||
|     abstract fun onSetValues(item: LibraryItem) | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Called when an item is released. | ||||
|      * | ||||
|      * @param position The position of the released item. | ||||
|      */ | ||||
|     override fun onItemReleased(position: Int) { | ||||
|         super.onItemReleased(position) | ||||
|         (adapter as? LibraryCategoryAdapter)?.onItemReleaseListener?.onItemReleased(position) | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -59,6 +59,13 @@ class LibraryItem(val manga: LibraryManga, private val libraryAsList: Preference | ||||
|         holder.onSetValues(this) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns true if this item is draggable. | ||||
|      */ | ||||
|     override fun isDraggable(): Boolean { | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Filters a manga depending on a query. | ||||
|      * | ||||
|   | ||||
| @@ -25,7 +25,7 @@ import kotlinx.android.synthetic.main.source_list_item.unread_text | ||||
|  | ||||
| class LibraryListHolder( | ||||
|     private val view: View, | ||||
|     private val adapter: FlexibleAdapter<*> | ||||
|     adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>> | ||||
| ) : LibraryHolder(view, adapter) { | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -214,6 +214,9 @@ class LibraryPresenter( | ||||
|                         ?: latestChapterManga.size | ||||
|                     manga1latestChapter.compareTo(manga2latestChapter) | ||||
|                 } | ||||
|                 LibrarySort.DRAG_AND_DROP -> { | ||||
|                     0 | ||||
|                 } | ||||
|                 else -> throw Exception("Unknown sorting mode") | ||||
|             } | ||||
|         } | ||||
| @@ -227,6 +230,12 @@ class LibraryPresenter( | ||||
|         return map.mapValues { entry -> entry.value.sortedWith(comparator) } | ||||
|     } | ||||
|  | ||||
|     private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { | ||||
|         return if (preferences.removeArticles().getOrDefault()) | ||||
|             i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true) | ||||
|         else i1.manga.title.compareTo(i2.manga.title, true) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the categories and all its manga from the database. | ||||
|      * | ||||
|   | ||||
| @@ -110,10 +110,11 @@ class LibrarySettingsSheet( | ||||
|             private val lastChecked = Item.MultiSort(R.string.action_sort_last_checked, this) | ||||
|             private val unread = Item.MultiSort(R.string.action_filter_unread, this) | ||||
|             private val latestChapter = Item.MultiSort(R.string.action_sort_latest_chapter, this) | ||||
| 			private val dragAndDrop = Item.MultiSort(R.string.action_sort_drag_and_drop, this) | ||||
|  | ||||
|             override val header = null | ||||
|             override val items = | ||||
|                 listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter) | ||||
|                 listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter, dragAndDrop) | ||||
|             override val footer = null | ||||
|  | ||||
|             override fun initModels() { | ||||
| @@ -135,6 +136,7 @@ class LibrarySettingsSheet( | ||||
|                 total.state = if (sorting == LibrarySort.TOTAL) order else Item.MultiSort.SORT_NONE | ||||
|                 latestChapter.state = | ||||
|                     if (sorting == LibrarySort.LATEST_CHAPTER) order else Item.MultiSort.SORT_NONE | ||||
|                 dragAndDrop.state = if (sorting == LibrarySort.DRAG_AND_DROP) order else SORT_NONE | ||||
|             } | ||||
|  | ||||
|             override fun onItemClicked(item: Item) { | ||||
| @@ -145,11 +147,14 @@ class LibrarySettingsSheet( | ||||
|                     (it as Item.MultiStateGroup).state = | ||||
|                         Item.MultiSort.SORT_NONE | ||||
|                 } | ||||
|                 item.state = when (prevState) { | ||||
|                     Item.MultiSort.SORT_NONE -> Item.MultiSort.SORT_ASC | ||||
|                     Item.MultiSort.SORT_ASC -> Item.MultiSort.SORT_DESC | ||||
|                     Item.MultiSort.SORT_DESC -> Item.MultiSort.SORT_ASC | ||||
|                     else -> throw Exception("Unknown state") | ||||
|                 if (item == dragAndDrop) | ||||
|                     item.state = SORT_ASC | ||||
|                 else | ||||
|                     item.state = when (prevState) { | ||||
|                         SORT_NONE -> SORT_ASC | ||||
|                         SORT_ASC -> SORT_DESC | ||||
|                         SORT_DESC -> SORT_ASC | ||||
|                         else -> throw Exception("Unknown state") | ||||
|                 } | ||||
|  | ||||
|                 preferences.librarySortingMode().set( | ||||
| @@ -160,6 +165,7 @@ class LibrarySettingsSheet( | ||||
|                         unread -> LibrarySort.UNREAD | ||||
|                         total -> LibrarySort.TOTAL | ||||
|                         latestChapter -> LibrarySort.LATEST_CHAPTER | ||||
| 						dragAndDrop -> LibrarySort.DRAG_AND_DROP | ||||
|                         else -> throw Exception("Unknown sorting") | ||||
|                     } | ||||
|                 ) | ||||
|   | ||||
| @@ -11,4 +11,5 @@ object LibrarySort { | ||||
|  | ||||
|     @Deprecated("Removed in favor of searching by source") | ||||
|     const val SOURCE = 5 | ||||
| } | ||||
|     const val DRAG_AND_DROP = 6 | ||||
| } | ||||
| @@ -41,4 +41,24 @@ | ||||
|         android:title="@string/label_migration" | ||||
|         app:showAsAction="never"/> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/action_reorganize" | ||||
|         android:title="@string/label_reorganize_by" | ||||
|         app:showAsAction="never"> | ||||
|         <menu> | ||||
|             <item | ||||
|                 android:id="@+id/action_alpha_asc" | ||||
|                 android:title="@string/action_sort_alpha"/> | ||||
|             <item | ||||
|                 android:id="@+id/action_alpha_dsc" | ||||
|                 android:title="@string/label_alpha_reverse"/> | ||||
|             <item | ||||
|                 android:id="@+id/action_update_asc" | ||||
|                 android:title="@string/action_sort_last_updated"/> | ||||
|             <item | ||||
|                 android:id="@+id/action_update_dsc" | ||||
|                 android:title="@string/action_sort_first_updated"/> | ||||
|         </menu> | ||||
|     </item> | ||||
|  | ||||
| </menu> | ||||
|   | ||||
| @@ -21,6 +21,10 @@ | ||||
|     <string name="label_categories">Categories</string> | ||||
|     <string name="label_backup">Backup</string> | ||||
|     <string name="label_migration">Source migration</string> | ||||
|     <string name="label_reorganize_by">Re-order</string> | ||||
|     <string name="label_alpha_reverse">Alphabetically (descending)</string> | ||||
|     <string name="label_hide_title">Hide title</string> | ||||
|     <string name="label_show_title">Show title</string> | ||||
|     <string name="label_extensions">Extensions</string> | ||||
|     <string name="label_extension_info">Extension info</string> | ||||
|     <string name="label_help">Help</string> | ||||
| @@ -42,6 +46,7 @@ | ||||
|     <string name="action_sort_last_read">Last read</string> | ||||
|     <string name="action_sort_last_checked">Last checked</string> | ||||
|     <string name="action_sort_latest_chapter">Latest chapter</string> | ||||
|     <string name="action_sort_drag_and_drop">Drag & Drop</string> | ||||
|     <string name="action_search">Search</string> | ||||
|     <string name="action_global_search">Global search</string> | ||||
|     <string name="action_select_all">Select all</string> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user