mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-07 23:38:40 +01:00
added import filters from blocks/mutes
This commit is contained in:
parent
6401776c77
commit
edc1ccce56
@ -72,7 +72,7 @@ public class FiltersData {
|
||||
@JsonObject
|
||||
@CursorObject(valuesCreator = true, tableInfo = true)
|
||||
public static class UserItem {
|
||||
@CursorField(value = Filters.Users._ID, type = TwidereDataStore.TYPE_PRIMARY_KEY)
|
||||
@CursorField(value = Filters.Users._ID, type = TwidereDataStore.TYPE_PRIMARY_KEY, excludeWrite = true)
|
||||
long _id;
|
||||
@CursorField(value = Filters.Users.USER_KEY, converter = UserKeyCursorFieldConverter.class, type = "TEXT NOT NULL UNIQUE")
|
||||
@JsonField(name = "user_key", typeConverter = UserKeyConverter.class)
|
||||
@ -88,7 +88,7 @@ public class FiltersData {
|
||||
*/
|
||||
@CursorField(value = Filters.Users.SOURCE, type = "INTEGER DEFAULT -1")
|
||||
@JsonField(name = "source")
|
||||
long source;
|
||||
long source = -1;
|
||||
|
||||
public UserKey getUserKey() {
|
||||
return userKey;
|
||||
@ -135,7 +135,7 @@ public class FiltersData {
|
||||
@JsonObject
|
||||
@CursorObject(valuesCreator = true, tableInfo = true)
|
||||
public static class BaseItem {
|
||||
@CursorField(value = Filters._ID, type = TwidereDataStore.TYPE_PRIMARY_KEY)
|
||||
@CursorField(value = Filters._ID, type = TwidereDataStore.TYPE_PRIMARY_KEY, excludeWrite = true)
|
||||
long _id;
|
||||
@CursorField(value = Filters.VALUE, type = "TEXT NOT NULL UNIQUE")
|
||||
@JsonField(name = "value")
|
||||
@ -145,7 +145,7 @@ public class FiltersData {
|
||||
*/
|
||||
@CursorField(value = Filters.Users.SOURCE, type = "INTEGER DEFAULT -1")
|
||||
@JsonField(name = "source")
|
||||
long source;
|
||||
long source = -1;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
|
@ -194,6 +194,9 @@ public class ParcelableUser implements Parcelable, Comparable<ParcelableUser> {
|
||||
@ParcelableThisPlease
|
||||
public String nickname;
|
||||
|
||||
@ParcelableThisPlease
|
||||
public boolean is_filtered;
|
||||
|
||||
public static final Creator<ParcelableUser> CREATOR = new Creator<ParcelableUser>() {
|
||||
@Override
|
||||
public ParcelableUser createFromParcel(Parcel source) {
|
||||
|
@ -93,9 +93,11 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Tabs;
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -1021,20 +1023,23 @@ public class DataStoreUtils implements Constants {
|
||||
since, sinceColumn, followingOnly, accountIds);
|
||||
}
|
||||
|
||||
public static void addToFilter(Context context, ParcelableUser user, boolean filterAnywhere) {
|
||||
public static void addToFilter(Context context, Collection<ParcelableUser> users, boolean filterAnywhere) {
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
final FiltersData.UserItem userItem = new FiltersData.UserItem();
|
||||
userItem.setUserKey(user.key);
|
||||
userItem.setScreenName(user.screen_name);
|
||||
userItem.setName(user.name);
|
||||
|
||||
try {
|
||||
// Insert to filtered users
|
||||
cr.insert(Filters.Users.CONTENT_URI, FiltersData$UserItemValuesCreator.create(userItem));
|
||||
if (filterAnywhere) {
|
||||
// Insert user mention to keywords
|
||||
List<ContentValues> userValues = new ArrayList<>();
|
||||
List<ContentValues> keywordValues = new ArrayList<>();
|
||||
List<ContentValues> linkValues = new ArrayList<>();
|
||||
for (ParcelableUser user : users) {
|
||||
final FiltersData.UserItem userItem = new FiltersData.UserItem();
|
||||
userItem.setUserKey(user.key);
|
||||
userItem.setScreenName(user.screen_name);
|
||||
userItem.setName(user.name);
|
||||
userValues.add(FiltersData$UserItemValuesCreator.create(userItem));
|
||||
|
||||
final FiltersData.BaseItem keywordItem = new FiltersData.BaseItem();
|
||||
keywordItem.setValue("@" + user.screen_name);
|
||||
cr.insert(Filters.Keywords.CONTENT_URI, FiltersData$BaseItemValuesCreator.create(keywordItem));
|
||||
keywordValues.add(FiltersData$BaseItemValuesCreator.create(keywordItem));
|
||||
|
||||
// Insert user link (without scheme) to links
|
||||
final FiltersData.BaseItem linkItem = new FiltersData.BaseItem();
|
||||
@ -1045,7 +1050,15 @@ public class DataStoreUtils implements Constants {
|
||||
linkWithoutScheme = linkWithoutScheme.substring(idx + 3);
|
||||
}
|
||||
linkItem.setValue(linkWithoutScheme);
|
||||
cr.insert(Filters.Links.CONTENT_URI, FiltersData$BaseItemValuesCreator.create(linkItem));
|
||||
linkValues.add(FiltersData$BaseItemValuesCreator.create(linkItem));
|
||||
}
|
||||
|
||||
ContentResolverUtils.bulkInsert(cr, Filters.Users.CONTENT_URI, userValues);
|
||||
if (filterAnywhere) {
|
||||
// Insert to filtered users
|
||||
ContentResolverUtils.bulkInsert(cr, Filters.Keywords.CONTENT_URI, keywordValues);
|
||||
// Insert user mention to keywords
|
||||
ContentResolverUtils.bulkInsert(cr, Filters.Links.CONTENT_URI, linkValues);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -21,9 +21,10 @@ package org.mariotaku.twidere.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
public class ActivatedCheckBox extends CheckBox {
|
||||
import org.mariotaku.chameleon.view.ChameleonCheckBox;
|
||||
|
||||
public class ActivatedCheckBox extends ChameleonCheckBox {
|
||||
|
||||
public ActivatedCheckBox(final Context context) {
|
||||
super(context);
|
||||
|
@ -29,7 +29,7 @@ fun Menu.setItemAvailability(id: Int, available: Boolean) {
|
||||
item.isEnabled = available
|
||||
}
|
||||
|
||||
fun Menu.setMenuGroupAvailability(groupId: Int, available: Boolean) {
|
||||
fun Menu.setGroupAvailability(groupId: Int, available: Boolean) {
|
||||
setGroupEnabled(groupId, available)
|
||||
setGroupVisible(groupId, available)
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
package org.mariotaku.ktextension
|
||||
|
||||
import android.util.SparseBooleanArray
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/27.
|
||||
*/
|
||||
operator fun SparseBooleanArray.set(key: Int, value: Boolean) {
|
||||
put(key, value)
|
||||
}
|
@ -39,7 +39,7 @@ class AddUserFilterDialogFragment : AbsUserMuteBlockDialogFragment() {
|
||||
}
|
||||
|
||||
override fun performUserAction(user: ParcelableUser, filterEverywhere: Boolean) {
|
||||
DataStoreUtils.addToFilter(context, user, filterEverywhere)
|
||||
DataStoreUtils.addToFilter(context, listOf(user), filterEverywhere)
|
||||
bus.post(FriendshipTaskEvent(FriendshipTaskEvent.Action.FILTER, user.account_key, user.key).apply {
|
||||
isFinished = true
|
||||
isSucceeded = true
|
||||
|
@ -43,6 +43,8 @@ import android.widget.AbsListView.MultiChoiceModeListener
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.ListView
|
||||
import kotlinx.android.synthetic.main.fragment_content_listview.*
|
||||
import org.mariotaku.ktextension.setGroupAvailability
|
||||
import org.mariotaku.ktextension.setItemAvailability
|
||||
import org.mariotaku.sqliteqb.library.Columns.Column
|
||||
import org.mariotaku.sqliteqb.library.Expression
|
||||
import org.mariotaku.sqliteqb.library.RawItemArray
|
||||
@ -92,11 +94,17 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
actionMode = mode
|
||||
setControlVisible(true)
|
||||
mode.menuInflater.inflate(R.menu.action_multi_select_items, menu)
|
||||
menu.setGroupAvailability(R.id.selection_group, true)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
updateTitle(mode)
|
||||
val checkedCount = listView.checkedItemCount
|
||||
val listCount = listView.count
|
||||
menu.setItemAvailability(R.id.select_none, checkedCount > 0)
|
||||
menu.setItemAvailability(R.id.select_all, checkedCount < listCount)
|
||||
menu.setItemAvailability(R.id.invert_selection, checkedCount > 0 && checkedCount < listCount)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -106,19 +114,28 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
val where = Expression.`in`(Column(Filters._ID),
|
||||
RawItemArray(listView.checkedItemIds))
|
||||
context.contentResolver.delete(contentUri, where.sql, null)
|
||||
mode.finish()
|
||||
}
|
||||
R.id.inverse_selection -> {
|
||||
R.id.select_all -> {
|
||||
for (i in 0 until listView.count) {
|
||||
listView.setItemChecked(i, true)
|
||||
}
|
||||
}
|
||||
R.id.select_none -> {
|
||||
for (i in 0 until listView.count) {
|
||||
listView.setItemChecked(i, false)
|
||||
}
|
||||
}
|
||||
R.id.invert_selection -> {
|
||||
val positions = listView.checkedItemPositions
|
||||
for (i in 0 until listView.count) {
|
||||
listView.setItemChecked(i, !positions.get(i))
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
mode.finish()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -129,6 +146,7 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long,
|
||||
checked: Boolean) {
|
||||
updateTitle(mode)
|
||||
mode.invalidate()
|
||||
}
|
||||
|
||||
override fun onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) {
|
||||
@ -161,6 +179,7 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
||||
} else {
|
||||
showEmpty(R.drawable.ic_info_volume_off, getString(R.string.no_rule))
|
||||
}
|
||||
actionMode?.invalidate()
|
||||
}
|
||||
|
||||
override fun onLoaderReset(loader: Loader<Cursor?>) {
|
||||
|
@ -1,35 +1,54 @@
|
||||
package org.mariotaku.twidere.fragment.filter
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.DialogFragment
|
||||
import android.support.v4.app.LoaderManager
|
||||
import android.support.v4.content.Loader
|
||||
import android.support.v4.util.ArrayMap
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.*
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import nl.komponents.kovenant.task
|
||||
import nl.komponents.kovenant.ui.alwaysUi
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.*
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.activity.BaseActivity
|
||||
import org.mariotaku.twidere.adapter.LoadMoreSupportAdapter
|
||||
import org.mariotaku.twidere.adapter.iface.IContentCardAdapter
|
||||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
|
||||
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition
|
||||
import org.mariotaku.twidere.constant.*
|
||||
import org.mariotaku.twidere.fragment.AbsContentListRecyclerViewFragment
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
import org.mariotaku.twidere.fragment.MessageDialogFragment
|
||||
import org.mariotaku.twidere.fragment.ProgressDialogFragment
|
||||
import org.mariotaku.twidere.loader.CursorSupportUsersLoader
|
||||
import org.mariotaku.twidere.loader.iface.IExtendedLoader
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
import org.mariotaku.twidere.util.ThemeUtils
|
||||
import org.mariotaku.twidere.util.support.ViewSupport
|
||||
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
|
||||
import org.mariotaku.twidere.view.holder.SimpleUserViewHolder
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/26.
|
||||
*/
|
||||
private const val EXTRA_COUNT = "count"
|
||||
|
||||
abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<BaseFiltersImportFragment.SelectableUsersAdapter>(),
|
||||
LoaderManager.LoaderCallbacks<List<ParcelableUser>?> {
|
||||
|
||||
|
||||
protected var nextCursor: Long = -1
|
||||
private set
|
||||
protected var prevCursor: Long = -1
|
||||
@ -39,11 +58,60 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Ba
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
val loaderArgs = Bundle(arguments)
|
||||
loaderArgs.putBoolean(IntentConstants.EXTRA_FROM_USER, true)
|
||||
loaderManager.initLoader(0, loaderArgs, this)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_filters_import, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
val checkedCount = adapter.checkedCount
|
||||
val userCount = adapter.userCount
|
||||
menu.setItemAvailability(R.id.select_none, checkedCount > 0)
|
||||
menu.setItemAvailability(R.id.select_all, checkedCount < userCount)
|
||||
menu.setItemAvailability(R.id.invert_selection, checkedCount > 0 && checkedCount < userCount)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.select_none -> {
|
||||
adapter.clearSelection()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
R.id.select_all -> {
|
||||
for (idx in rangeOfSize(adapter.userStartIndex, adapter.userCount - 1)) {
|
||||
adapter.setItemChecked(idx, true)
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
R.id.invert_selection -> {
|
||||
for (idx in rangeOfSize(adapter.userStartIndex, adapter.userCount - 1)) {
|
||||
adapter.setItemChecked(idx, !adapter.isItemChecked(idx))
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
R.id.perform_import -> {
|
||||
if (adapter.checkedCount == 0) {
|
||||
Toast.makeText(context, R.string.message_no_user_selected, Toast.LENGTH_SHORT).show()
|
||||
return true
|
||||
}
|
||||
val df = ImportConfirmDialogFragment()
|
||||
df.arguments = Bundle {
|
||||
this[EXTRA_COUNT] = adapter.checkedCount
|
||||
}
|
||||
df.show(childFragmentManager, "import_confirm")
|
||||
}
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle): Loader<List<ParcelableUser>?> {
|
||||
val fromUser = args.getBoolean(IntentConstants.EXTRA_FROM_USER)
|
||||
args.remove(IntentConstants.EXTRA_FROM_USER)
|
||||
@ -55,22 +123,35 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Ba
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<List<ParcelableUser>?>, data: List<ParcelableUser>?) {
|
||||
val hasMoreData = run {
|
||||
val previousCount = adapter.data?.size
|
||||
if (previousCount != data?.size) return@run true
|
||||
val previousFirst = adapter.data?.firstOrNull()
|
||||
val previousLast = adapter.data?.lastOrNull()
|
||||
// If first and last data not changed, assume no more data
|
||||
return@run previousFirst != data?.firstOrNull() && previousLast != data?.lastOrNull()
|
||||
}
|
||||
adapter.data = data
|
||||
if (loader !is IExtendedLoader || loader.fromUser) {
|
||||
adapter.loadMoreSupportedPosition = if (hasMoreData(data)) ILoadMoreSupportAdapter.END else ILoadMoreSupportAdapter.NONE
|
||||
adapter.loadMoreSupportedPosition = if (hasMoreData) {
|
||||
ILoadMoreSupportAdapter.END
|
||||
} else {
|
||||
ILoadMoreSupportAdapter.NONE
|
||||
}
|
||||
refreshEnabled = true
|
||||
}
|
||||
if (loader is IExtendedLoader) {
|
||||
loader.fromUser = false
|
||||
}
|
||||
showContent()
|
||||
refreshEnabled = true
|
||||
refreshEnabled = data.isNullOrEmpty()
|
||||
refreshing = false
|
||||
setLoadMoreIndicatorPosition(ILoadMoreSupportAdapter.NONE)
|
||||
val cursorLoader = loader as CursorSupportUsersLoader
|
||||
nextCursor = cursorLoader.nextCursor
|
||||
prevCursor = cursorLoader.prevCursor
|
||||
nextPage = cursorLoader.nextPage
|
||||
activity.supportInvalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
|
||||
@ -85,21 +166,72 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Ba
|
||||
loaderManager.restartLoader(0, loaderArgs, this)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_filters_import, container, false)
|
||||
}
|
||||
|
||||
override fun onCreateAdapter(context: Context): SelectableUsersAdapter {
|
||||
return SelectableUsersAdapter(context)
|
||||
val adapter = SelectableUsersAdapter(context)
|
||||
adapter.itemCheckedListener = { position, checked ->
|
||||
val count = adapter.checkedCount
|
||||
val actionBar = (activity as BaseActivity).supportActionBar
|
||||
actionBar?.subtitle = if (count > 0) {
|
||||
resources.getQuantityString(R.plurals.Nitems_selected, count, count)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
activity.supportInvalidateOptionsMenu()
|
||||
}
|
||||
return adapter
|
||||
}
|
||||
|
||||
protected abstract fun onCreateUsersLoader(context: Context, args: Bundle, fromUser: Boolean):
|
||||
Loader<List<ParcelableUser>?>
|
||||
|
||||
protected open fun hasMoreData(data: List<ParcelableUser>?): Boolean {
|
||||
return data == null || !data.isEmpty()
|
||||
private fun performImport(filterEverywhere: Boolean) {
|
||||
val selectedUsers = rangeOfSize(adapter.userStartIndex, adapter.userCount - 1)
|
||||
.filter { adapter.isItemChecked(it) }
|
||||
.map { adapter.getUser(it)!! }
|
||||
ProgressDialogFragment.show(childFragmentManager, "import_progress")
|
||||
task {
|
||||
DataStoreUtils.addToFilter(context, selectedUsers, filterEverywhere)
|
||||
}.alwaysUi {
|
||||
executeAfterFragmentResumed {
|
||||
(childFragmentManager.findFragmentByTag("import_progress") as? DialogFragment)?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ImportConfirmDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
|
||||
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
val filterEverywhere = ((dialog as Dialog).findViewById(R.id.filterEverywhereToggle) as CheckBox).isChecked
|
||||
(parentFragment as BaseFiltersImportFragment).performImport(filterEverywhere)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(context)
|
||||
builder.setTitle(R.string.add_to_filter)
|
||||
builder.setView(R.layout.dialog_block_mute_filter_user_confirm)
|
||||
builder.setPositiveButton(android.R.string.ok, this)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener {
|
||||
val confirmMessageView = dialog.findViewById(R.id.confirmMessage) as TextView
|
||||
val filterEverywhereHelp = dialog.findViewById(R.id.filterEverywhereHelp)!!
|
||||
filterEverywhereHelp.setOnClickListener {
|
||||
MessageDialogFragment.show(childFragmentManager, title = getString(R.string.filter_everywhere),
|
||||
message = getString(R.string.filter_everywhere_description), tag = "filter_everywhere_help")
|
||||
}
|
||||
val usersCount = arguments.getInt(EXTRA_COUNT)
|
||||
val nUsers = resources.getQuantityString(R.plurals.N_users, usersCount, usersCount)
|
||||
confirmMessageView.text = getString(R.string.filter_user_confirm_message, nUsers)
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SelectableUsersAdapter(context: Context) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(context), IContentCardAdapter {
|
||||
override val isShowAbsoluteTime: Boolean
|
||||
override val profileImageEnabled: Boolean
|
||||
@ -109,16 +241,23 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Ba
|
||||
val ITEM_VIEW_TYPE_USER = 2
|
||||
|
||||
private val inflater: LayoutInflater
|
||||
private val itemStates: MutableMap<UserKey, Boolean>
|
||||
var itemCheckedListener: ((Int, Boolean) -> Unit)? = null
|
||||
|
||||
var data: List<ParcelableUser>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
value?.forEach { item ->
|
||||
if (item.key !in itemStates && item.is_filtered) {
|
||||
itemStates[item.key] = true
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
inflater = LayoutInflater.from(context)
|
||||
itemStates = ArrayMap<UserKey, Boolean>()
|
||||
isShowAbsoluteTime = preferences[showAbsoluteTimeKey]
|
||||
profileImageEnabled = preferences[displayProfileImageKey]
|
||||
profileImageStyle = preferences[profileImageStyleKey]
|
||||
@ -157,9 +296,8 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Ba
|
||||
return start
|
||||
}
|
||||
|
||||
fun getUserId(position: Int): String? {
|
||||
if (position == userCount) return null
|
||||
return data!![position].key.id
|
||||
fun getUserKey(position: Int): UserKey {
|
||||
return data!![position].key
|
||||
}
|
||||
|
||||
val userCount: Int
|
||||
@ -229,15 +367,48 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Ba
|
||||
}
|
||||
return ITEM_VIEW_TYPE_USER
|
||||
}
|
||||
|
||||
val checkedCount: Int get() {
|
||||
return itemStates.count { it.value }
|
||||
}
|
||||
|
||||
fun setItemChecked(position: Int, value: Boolean) {
|
||||
val userKey = getUserKey(position)
|
||||
itemStates[userKey] = value
|
||||
itemCheckedListener?.invoke(position, value)
|
||||
}
|
||||
|
||||
fun isItemChecked(position: Int): Boolean {
|
||||
return itemStates[getUserKey(position)] ?: false
|
||||
}
|
||||
|
||||
fun clearSelection() {
|
||||
itemStates.clear()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SelectableUserViewHolder(
|
||||
internal class SelectableUserViewHolder(
|
||||
itemView: View,
|
||||
adapter: IContentCardAdapter
|
||||
adapter: SelectableUsersAdapter
|
||||
) : SimpleUserViewHolder(itemView, adapter) {
|
||||
val checkChangedListener: CompoundButton.OnCheckedChangeListener
|
||||
|
||||
init {
|
||||
ViewSupport.setBackground(itemView, ThemeUtils.getSelectableItemBackgroundDrawable(itemView.context))
|
||||
checkBox.visibility = View.VISIBLE
|
||||
checkChangedListener = CompoundButton.OnCheckedChangeListener { view, value ->
|
||||
adapter.setItemChecked(layoutPosition, value)
|
||||
}
|
||||
itemView.setOnClickListener {
|
||||
checkBox.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun displayUser(user: ParcelableUser) {
|
||||
super.displayUser(user)
|
||||
checkBox.setOnCheckedChangeListener(null)
|
||||
checkBox.isChecked = (adapter as SelectableUsersAdapter).isItemChecked(layoutPosition)
|
||||
checkBox.setOnCheckedChangeListener(checkChangedListener)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,13 +64,19 @@ abstract class MicroBlogAPIUsersLoader(
|
||||
if (hasId(user.id)) {
|
||||
continue
|
||||
}
|
||||
data.add(ParcelableUserUtils.fromUser(user, accountKey, pos.toLong()))
|
||||
val item = ParcelableUserUtils.fromUser(user, accountKey, pos.toLong())
|
||||
processUser(item)
|
||||
data.add(item)
|
||||
pos++
|
||||
}
|
||||
Collections.sort(data)
|
||||
return ListResponse.getListInstance(data)
|
||||
}
|
||||
|
||||
protected open fun processUser(user: ParcelableUser) {
|
||||
|
||||
}
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
protected abstract fun getUsers(twitter: MicroBlog, details: AccountDetails): List<User>
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import org.mariotaku.microblog.library.twitter.model.User
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
|
||||
class MutesUsersLoader(
|
||||
context: Context,
|
||||
@ -36,9 +37,19 @@ class MutesUsersLoader(
|
||||
fromUser: Boolean
|
||||
) : CursorSupportUsersLoader(context, accountKey, data, fromUser) {
|
||||
|
||||
private var filteredUsers: Array<UserKey>? = null
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getCursoredUsers(twitter: MicroBlog, details: AccountDetails, paging: Paging): PageableResponseList<User> {
|
||||
return twitter.getMutesUsersList(paging)
|
||||
}
|
||||
|
||||
override fun onLoadInBackground(): List<ParcelableUser> {
|
||||
filteredUsers = DataStoreUtils.getFilteredUserIds(context)
|
||||
return super.onLoadInBackground()
|
||||
}
|
||||
|
||||
override fun processUser(user: ParcelableUser) {
|
||||
user.is_filtered = filteredUsers?.contains(user.key) ?: false
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.ParcelableUser
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.util.DataStoreUtils
|
||||
|
||||
class UserBlocksLoader(
|
||||
context: Context,
|
||||
@ -37,6 +38,8 @@ class UserBlocksLoader(
|
||||
fromUser: Boolean
|
||||
) : CursorSupportUsersLoader(context, accountKey, data, fromUser) {
|
||||
|
||||
private var filteredUsers: Array<UserKey>? = null
|
||||
|
||||
@Throws(MicroBlogException::class)
|
||||
override fun getCursoredUsers(twitter: MicroBlog,
|
||||
details: AccountDetails,
|
||||
@ -49,4 +52,12 @@ class UserBlocksLoader(
|
||||
return twitter.getBlocksList(paging)
|
||||
}
|
||||
|
||||
override fun onLoadInBackground(): List<ParcelableUser> {
|
||||
filteredUsers = DataStoreUtils.getFilteredUserIds(context)
|
||||
return super.onLoadInBackground()
|
||||
}
|
||||
|
||||
override fun processUser(user: ParcelableUser) {
|
||||
user.is_filtered = filteredUsers?.contains(user.key) ?: false
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package org.mariotaku.twidere.model.analyzer
|
||||
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.util.Analyzer
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/28.
|
||||
*/
|
||||
|
||||
data class UpdateStatus(
|
||||
@AccountType override val accountType: String? = null
|
||||
) : Analyzer.Event {
|
||||
|
||||
}
|
@ -66,7 +66,7 @@ open class CreateUserBlockTask(
|
||||
resolver.insert(CachedRelationships.CONTENT_URI, values)
|
||||
|
||||
if (filterEverywhere) {
|
||||
DataStoreUtils.addToFilter(context, user, true)
|
||||
DataStoreUtils.addToFilter(context, listOf(user), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ class CreateUserMuteTask(
|
||||
values.put(CachedRelationships.MUTING, true)
|
||||
resolver.insert(CachedRelationships.CONTENT_URI, values)
|
||||
if (filterEverywhere) {
|
||||
DataStoreUtils.addToFilter(context, user, true)
|
||||
DataStoreUtils.addToFilter(context, listOf(user), true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ fun AccountManager.removeAccountSupport(
|
||||
return AccountManagerSupportL.removeAccount(this, account, activity, callback, handler)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val future = this.removeAccount(account, { future ->
|
||||
callback?.run(BooleanToBundleAccountManagerFuture(future))
|
||||
}, handler)
|
||||
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
layout="@layout/fragment_content_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
|
||||
</RelativeLayout>
|
@ -51,7 +51,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
@ -61,7 +61,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/screenName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
|
@ -1,11 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="AppCompatResource">
|
||||
|
||||
<item
|
||||
android:id="@id/delete"
|
||||
android:icon="@drawable/ic_action_delete"
|
||||
app:showAsAction="ifRoom|withText"
|
||||
android:title="@string/action_delete" />
|
||||
|
||||
android:showAsAction="always"
|
||||
android:title="@string/action_delete"
|
||||
app:showAsAction="always"
|
||||
tools:ignore="AlwaysShowAction"/>
|
||||
<group
|
||||
android:id="@+id/selection_group"
|
||||
android:enabled="false"
|
||||
android:visible="false">
|
||||
<item
|
||||
android:id="@+id/select_all"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/action_select_all"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/select_none"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/action_select_none"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/invert_selection"
|
||||
android:showAsAction="never"
|
||||
android:title="@string/action_invert_selection"
|
||||
app:showAsAction="never"/>
|
||||
</group>
|
||||
</menu>
|
21
twidere/src/main/res/menu/menu_filters_import.xml
Normal file
21
twidere/src/main/res/menu/menu_filters_import.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/perform_import"
|
||||
android:icon="@drawable/ic_action_import"
|
||||
android:title="@string/action_import"
|
||||
app:showAsAction="always"/>
|
||||
<item
|
||||
android:id="@+id/select_all"
|
||||
android:title="@string/action_select_all"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/select_none"
|
||||
android:title="@string/action_select_none"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/invert_selection"
|
||||
android:title="@string/action_invert_selection"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
@ -78,7 +78,6 @@
|
||||
<item name="progress" type="id"/>
|
||||
<item name="open_with_account" type="id"/>
|
||||
<item name="accounts" type="id"/>
|
||||
<item name="inverse_selection" type="id"/>
|
||||
<item name="edit_media" type="id"/>
|
||||
<item name="reset" type="id"/>
|
||||
<item name="info" type="id"/>
|
||||
|
@ -61,5 +61,9 @@
|
||||
<item quantity="one"><xliff:g id="count">%d</xliff:g> vote</item>
|
||||
<item quantity="other"><xliff:g id="count">%d</xliff:g> votes</item>
|
||||
</plurals>
|
||||
<plurals name="N_users">
|
||||
<item quantity="one"><xliff:g id="count">%d</xliff:g> user</item>
|
||||
<item quantity="other"><xliff:g id="count">%d</xliff:g> users</item>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
@ -578,7 +578,6 @@
|
||||
<string name="exclude_this_host">Exclude this host</string>
|
||||
<string name="api_url_format_help">[DOMAIN]: Twitter API domain.\nExample: https://[DOMAIN].twitter.com/ will be replaced to https://api.twitter.com/.</string>
|
||||
<string name="no_version_suffix">No version suffix</string>
|
||||
<string name="inverse_selection">Inverse selection</string>
|
||||
<string name="edit_media">Edit media</string>
|
||||
<string name="media">Media</string>
|
||||
<string name="mute_user">Mute <xliff:g id="name">%s</xliff:g></string>
|
||||
@ -859,4 +858,10 @@
|
||||
<string name="title_error_invalid_account">Invalid account</string>
|
||||
<string name="message_error_invalid_account">Some account data are corrupted, Twidere will remove those accounts to prevent crash.</string>
|
||||
<string name="title_premium_features_name">Twidere ∞</string>
|
||||
<!-- [verb] Perform import action -->
|
||||
<string name="action_import">Import</string>
|
||||
<string name="action_select_all">Select all</string>
|
||||
<string name="action_select_none">Select none</string>
|
||||
<string name="action_invert_selection">Invert selection</string>
|
||||
<string name="message_no_user_selected">No user selected</string>
|
||||
</resources>
|
13
twidere/src/main/svg/drawable/ic_action_import-mdpi.svg
Normal file
13
twidere/src/main/svg/drawable/ic_action_import-mdpi.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 41.2 (35397) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>ic_action_import-mdpi</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Action-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ic_action_import-mdpi">
|
||||
<polyline id="Path-2" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="11.7563613 7 7 7 7 25 25 25 25 20.406492"></polyline>
|
||||
<path d="M16,8.13011457 L18.7765957,10.9042553 L24.6783961,5 L28,8.32160393 L22.0957447,14.2234043 L24.8698854,16.997545 L16,16.997545 L16,8.13011457 Z" id="polygon3-#1-path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 907 B |
Loading…
x
Reference in New Issue
Block a user