migrating to kotlin

This commit is contained in:
Mariotaku Lee 2016-07-08 15:52:32 +08:00
parent b83ad85c95
commit 7319655443
20 changed files with 987 additions and 1190 deletions

View File

@ -1,354 +0,0 @@
package org.mariotaku.twidere.fragment;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.util.SimpleArrayMap;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ImageView;
import android.widget.TextView;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
import org.mariotaku.sqliteqb.library.ArgsArray;
import org.mariotaku.sqliteqb.library.Columns;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.ColorPickerDialogActivity;
import org.mariotaku.twidere.activity.SignInActivity;
import org.mariotaku.twidere.adapter.AccountsAdapter;
import org.mariotaku.twidere.annotation.Referral;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox;
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.util.IntentUtils;
import org.mariotaku.twidere.util.TwidereCollectionUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.collection.CompactHashSet;
import java.util.ArrayList;
import java.util.Set;
import static org.mariotaku.twidere.Constants.EXTRA_ALPHA_SLIDER;
import static org.mariotaku.twidere.Constants.EXTRA_COLOR;
import static org.mariotaku.twidere.Constants.EXTRA_ID;
import static org.mariotaku.twidere.Constants.INTENT_ACTION_TWITTER_LOGIN;
import static org.mariotaku.twidere.Constants.KEY_DEFAULT_ACCOUNT_KEY;
import static org.mariotaku.twidere.Constants.KEY_NEW_DOCUMENT_API;
import static org.mariotaku.twidere.Constants.REQUEST_SET_COLOR;
/**
* Created by mariotaku on 14/10/26.
*/
public class AccountsManagerFragment extends BaseSupportFragment implements LoaderCallbacks<Cursor>,
DropListener, OnSharedPreferenceChangeListener, AdapterView.OnItemClickListener, AccountsAdapter.OnAccountToggleListener {
private static final String FRAGMENT_TAG_ACCOUNT_DELETION = "account_deletion";
private AccountsAdapter mAdapter;
private ParcelableAccount mSelectedAccount;
private SimpleArrayMap<UserKey, Boolean> mActivatedState = new SimpleArrayMap<>();
private DragSortListView mListView;
private View mEmptyView;
private View mListContainer, mProgressContainer;
private TextView mEmptyText;
private ImageView mEmptyIcon;
private void setListShown(boolean shown) {
mListContainer.setVisibility(shown ? View.VISIBLE : View.GONE);
mProgressContainer.setVisibility(shown ? View.GONE : View.VISIBLE);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_account: {
final Intent intent = new Intent(INTENT_ACTION_TWITTER_LOGIN);
intent.setClass(getActivity(), SignInActivity.class);
startActivity(intent);
break;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_SET_COLOR: {
if (resultCode != Activity.RESULT_OK || data == null || mSelectedAccount == null)
return;
final ContentValues values = new ContentValues();
values.put(Accounts.COLOR, data.getIntExtra(EXTRA_COLOR, Color.WHITE));
final Expression where = Expression.equalsArgs(Accounts.ACCOUNT_KEY);
final String[] whereArgs = {mSelectedAccount.account_key.toString()};
final ContentResolver cr = getContentResolver();
cr.update(Accounts.CONTENT_URI, values, where.getSQL(), whereArgs);
return;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_accounts_manager, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final ContextMenuInfo menuInfo = item.getMenuInfo();
if (!(menuInfo instanceof AdapterContextMenuInfo)) return false;
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
ParcelableAccount account = mAdapter.getAccount(info.position);
mSelectedAccount = account;
if (account == null) return false;
switch (item.getItemId()) {
case R.id.set_color: {
final Intent intent = new Intent(getActivity(), ColorPickerDialogActivity.class);
intent.putExtra(EXTRA_COLOR, account.color);
intent.putExtra(EXTRA_ALPHA_SLIDER, false);
startActivityForResult(intent, REQUEST_SET_COLOR);
break;
}
case R.id.delete: {
final AccountDeletionDialogFragment f = new AccountDeletionDialogFragment();
final Bundle args = new Bundle();
args.putLong(EXTRA_ID, account.id);
f.setArguments(args);
f.show(getChildFragmentManager(), FRAGMENT_TAG_ACCOUNT_DELETION);
break;
}
}
return false;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Context context = getContext();
if (context == null) return;
final ParcelableAccount account = mAdapter.getAccount(position);
if (account.account_user != null) {
IntentUtils.openUserProfile(context, account.account_user, null,
preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE);
} else {
IntentUtils.openUserProfile(context, account.account_key, account.account_key,
account.screen_name, null, preferences.getBoolean(KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE);
}
}
@Override
public void onStop() {
super.onStop();
saveActivatedState();
}
private void saveActivatedState() {
final Set<UserKey> trueIds = new CompactHashSet<>(), falseIds = new CompactHashSet<>();
for (int i = 0, j = mActivatedState.size(); i < j; i++) {
if (mActivatedState.valueAt(i)) {
trueIds.add(mActivatedState.keyAt(i));
} else {
falseIds.add(mActivatedState.keyAt(i));
}
}
final ContentResolver cr = getContentResolver();
final ContentValues values = new ContentValues();
values.put(Accounts.IS_ACTIVATED, true);
Expression where = Expression.in(new Columns.Column(Accounts.ACCOUNT_KEY), new ArgsArray(trueIds.size()));
String[] whereArgs = TwidereCollectionUtils.toStringArray(trueIds);
cr.update(Accounts.CONTENT_URI, values, where.getSQL(), whereArgs);
values.put(Accounts.IS_ACTIVATED, false);
where = Expression.in(new Columns.Column(Accounts.ACCOUNT_KEY), new ArgsArray(falseIds.size()));
whereArgs = TwidereCollectionUtils.toStringArray(falseIds);
cr.update(Accounts.CONTENT_URI, values, where.getSQL(), whereArgs);
}
@Override
public void onAccountToggle(UserKey accountId, boolean state) {
mActivatedState.put(accountId, state);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mListView = (DragSortListView) view.findViewById(android.R.id.list);
mEmptyView = view.findViewById(android.R.id.empty);
mEmptyIcon = (ImageView) view.findViewById(R.id.empty_icon);
mEmptyText = (TextView) view.findViewById(R.id.empty_text);
mListContainer = view.findViewById(R.id.list_container);
mProgressContainer = view.findViewById(R.id.progress_container);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (!(menuInfo instanceof AdapterContextMenuInfo)) return;
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
final ParcelableAccount account = mAdapter.getAccount(info.position);
menu.setHeaderTitle(account.name);
final MenuInflater inflater = new MenuInflater(v.getContext());
inflater.inflate(R.menu.action_manager_account, menu);
}
@Override
public void onDestroyView() {
preferences.unregisterOnSharedPreferenceChangeListener(this);
super.onDestroyView();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
final FragmentActivity activity = getActivity();
preferences.registerOnSharedPreferenceChangeListener(this);
mAdapter = new AccountsAdapter(activity);
Utils.configBaseAdapter(activity, mAdapter);
mAdapter.setSortEnabled(true);
mAdapter.setSwitchEnabled(true);
mAdapter.setOnAccountToggleListener(this);
mListView.setAdapter(mAdapter);
mListView.setDragEnabled(true);
mListView.setDropListener(this);
mListView.setOnItemClickListener(this);
mListView.setOnCreateContextMenuListener(this);
mListView.setEmptyView(mEmptyView);
mEmptyText.setText(R.string.no_account);
mEmptyIcon.setImageResource(R.drawable.ic_info_error_generic);
getLoaderManager().initLoader(0, null, this);
setListShown(false);
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.layout_draggable_list_with_empty_view, container, false);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
final Uri uri = Accounts.CONTENT_URI;
return new CursorLoader(getActivity(), uri, Accounts.COLUMNS, null, null, Accounts.SORT_POSITION);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
setListShown(true);
mAdapter.changeCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.changeCursor(null);
}
@Override
public void drop(int from, int to) {
mAdapter.drop(from, to);
if (mListView.getChoiceMode() != AbsListView.CHOICE_MODE_NONE) {
mListView.moveCheckState(from, to);
}
saveAccountPositions();
}
private void saveAccountPositions() {
final ContentResolver cr = getContentResolver();
final ArrayList<Integer> positions = mAdapter.getCursorPositions();
final Cursor c = mAdapter.getCursor();
if (positions != null && c != null && !c.isClosed()) {
final int idIdx = c.getColumnIndex(Accounts._ID);
for (int i = 0, j = positions.size(); i < j; i++) {
c.moveToPosition(positions.get(i));
final long id = c.getLong(idIdx);
final ContentValues values = new ContentValues();
values.put(Accounts.SORT_POSITION, i);
final Expression where = Expression.equals(Accounts._ID, id);
cr.update(Accounts.CONTENT_URI, values, where.getSQL(), null);
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (KEY_DEFAULT_ACCOUNT_KEY.equals(key)) {
updateDefaultAccount();
}
}
private void updateDefaultAccount() {
mAdapter.notifyDataSetChanged();
}
public static final class AccountDeletionDialogFragment extends BaseDialogFragment implements
DialogInterface.OnClickListener {
@Override
public void onClick(final DialogInterface dialog, final int which) {
final Bundle args = getArguments();
final long id = args.getLong(EXTRA_ID);
final ContentResolver resolver = getContentResolver();
switch (which) {
case DialogInterface.BUTTON_POSITIVE: {
final String where = Expression.equalsArgs(Accounts._ID)
.getSQL();
final String[] whereArgs = {String.valueOf(id)};
resolver.delete(Accounts.CONTENT_URI, where, whereArgs);
// Also delete tweets related to the account we previously
// deleted.
resolver.delete(Statuses.CONTENT_URI, where, whereArgs);
resolver.delete(Mentions.CONTENT_URI, where, whereArgs);
resolver.delete(Inbox.CONTENT_URI, where, whereArgs);
resolver.delete(Outbox.CONTENT_URI, where, whereArgs);
break;
}
}
}
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Context context = getContext();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setNegativeButton(android.R.string.cancel, null);
builder.setPositiveButton(android.R.string.ok, this);
builder.setTitle(R.string.account_delete_confirm_title);
builder.setMessage(R.string.account_delete_confirm_message);
return builder.create();
}
}
}

View File

@ -1,255 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AlertDialog;
import android.util.SparseBooleanArray;
import com.twitter.Extractor;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableUserMention;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.HtmlEscapeHelper;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.UserColorNameManager;
import org.mariotaku.twidere.util.content.ContentResolverUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class AddStatusFilterDialogFragment extends BaseDialogFragment {
public static final String FRAGMENT_TAG = "add_status_filter";
private final Extractor mExtractor = new Extractor();
private FilterItemInfo[] mFilterItems;
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
mFilterItems = getFilterItemsInfo();
final String[] entries = new String[mFilterItems.length];
final boolean nameFirst = preferences.getBoolean(KEY_NAME_FIRST);
for (int i = 0, j = entries.length; i < j; i++) {
final FilterItemInfo info = mFilterItems[i];
switch (info.type) {
case FilterItemInfo.FILTER_TYPE_USER:
entries[i] = getString(R.string.user_filter_name, getName(userColorNameManager,
info.value, nameFirst));
break;
case FilterItemInfo.FILTER_TYPE_KEYWORD:
entries[i] = getString(R.string.keyword_filter_name, getName(userColorNameManager,
info.value, nameFirst));
break;
case FilterItemInfo.FILTER_TYPE_SOURCE:
entries[i] = getString(R.string.source_filter_name, getName(userColorNameManager,
info.value, nameFirst));
break;
}
}
builder.setTitle(R.string.add_to_filter);
builder.setMultiChoiceItems(entries, null, null);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AlertDialog alertDialog = ((AlertDialog) dialog);
final SparseBooleanArray checkPositions = alertDialog.getListView().getCheckedItemPositions();
final Set<UserKey> userKeys = new HashSet<>();
final Set<String> keywords = new HashSet<>();
final Set<String> sources = new HashSet<>();
final ArrayList<ContentValues> userValues = new ArrayList<>();
final ArrayList<ContentValues> keywordValues = new ArrayList<>();
final ArrayList<ContentValues> sourceValues = new ArrayList<>();
for (int i = 0, j = checkPositions.size(); i < j; i++) {
if (!checkPositions.valueAt(i)) continue;
final FilterItemInfo info = mFilterItems[checkPositions.keyAt(i)];
final Object value = info.value;
if (value instanceof ParcelableUserMention) {
final ParcelableUserMention mention = (ParcelableUserMention) value;
userKeys.add(mention.key);
userValues.add(ContentValuesCreator.createFilteredUser(mention));
} else if (value instanceof UserItem) {
final UserItem item = (UserItem) value;
userKeys.add(item.key);
userValues.add(createFilteredUser(item));
} else if (info.type == FilterItemInfo.FILTER_TYPE_KEYWORD) {
if (value != null) {
final String keyword = ParseUtils.parseString(value);
keywords.add(keyword);
final ContentValues values = new ContentValues();
values.put(Filters.Keywords.VALUE, "#" + keyword);
keywordValues.add(values);
}
} else if (info.type == FilterItemInfo.FILTER_TYPE_SOURCE) {
if (value != null) {
final String source = ParseUtils.parseString(value);
sources.add(source);
final ContentValues values = new ContentValues();
values.put(Filters.Sources.VALUE, source);
sourceValues.add(values);
}
}
}
final ContentResolver resolver = getContentResolver();
ContentResolverUtils.bulkDelete(resolver, Filters.Users.CONTENT_URI, Filters.Users.USER_KEY, userKeys, null);
ContentResolverUtils.bulkDelete(resolver, Filters.Keywords.CONTENT_URI, Filters.Keywords.VALUE, keywords, null);
ContentResolverUtils.bulkDelete(resolver, Filters.Sources.CONTENT_URI, Filters.Sources.VALUE, sources, null);
ContentResolverUtils.bulkInsert(resolver, Filters.Users.CONTENT_URI, userValues);
ContentResolverUtils.bulkInsert(resolver, Filters.Keywords.CONTENT_URI, keywordValues);
ContentResolverUtils.bulkInsert(resolver, Filters.Sources.CONTENT_URI, sourceValues);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
private FilterItemInfo[] getFilterItemsInfo() {
final Bundle args = getArguments();
if (args == null || !args.containsKey(EXTRA_STATUS)) return new FilterItemInfo[0];
final ParcelableStatus status = args.getParcelable(EXTRA_STATUS);
if (status == null) return new FilterItemInfo[0];
final ArrayList<FilterItemInfo> list = new ArrayList<>();
if (status.is_retweet) {
list.add(new FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER, new UserItem(status.retweeted_by_user_key,
status.retweeted_by_user_name, status.retweeted_by_user_screen_name)));
}
if (status.is_quote) {
list.add(new FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER, new UserItem(status.quoted_user_key,
status.quoted_user_name, status.quoted_user_screen_name)));
}
list.add(new FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER, new UserItem(status.user_key,
status.user_name, status.user_screen_name)));
final ParcelableUserMention[] mentions = status.mentions;
if (mentions != null) {
for (final ParcelableUserMention mention : mentions) {
if (!mention.key.equals(status.user_key)) {
list.add(new FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER, mention));
}
}
}
final HashSet<String> hashtags = new HashSet<>();
hashtags.addAll(mExtractor.extractHashtags(status.text_plain));
for (final String hashtag : hashtags) {
list.add(new FilterItemInfo(FilterItemInfo.FILTER_TYPE_KEYWORD, hashtag));
}
final String source = HtmlEscapeHelper.toPlainText(status.source);
list.add(new FilterItemInfo(FilterItemInfo.FILTER_TYPE_SOURCE, source));
return list.toArray(new FilterItemInfo[list.size()]);
}
private String getName(final UserColorNameManager manager, final Object value, boolean nameFirst) {
if (value instanceof ParcelableUserMention) {
final ParcelableUserMention mention = (ParcelableUserMention) value;
return manager.getDisplayName(mention.key, mention.name, mention.screen_name, nameFirst
);
} else if (value instanceof UserItem) {
final UserItem item = (UserItem) value;
return manager.getDisplayName(item.key, item.name, item.screen_name, nameFirst);
} else
return ParseUtils.parseString(value);
}
private static ContentValues createFilteredUser(UserItem item) {
if (item == null) return null;
final ContentValues values = new ContentValues();
values.put(Filters.Users.USER_KEY, item.key.toString());
values.put(Filters.Users.NAME, item.name);
values.put(Filters.Users.SCREEN_NAME, item.screen_name);
return values;
}
public static AddStatusFilterDialogFragment show(final FragmentManager fm, final ParcelableStatus status) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_STATUS, status);
final AddStatusFilterDialogFragment f = new AddStatusFilterDialogFragment();
f.setArguments(args);
f.show(fm, FRAGMENT_TAG);
return f;
}
private static class FilterItemInfo {
static final int FILTER_TYPE_USER = 1;
static final int FILTER_TYPE_KEYWORD = 2;
static final int FILTER_TYPE_SOURCE = 3;
final int type;
final Object value;
FilterItemInfo(final int type, final Object value) {
this.type = type;
this.value = value;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof FilterItemInfo)) return false;
final FilterItemInfo other = (FilterItemInfo) obj;
if (type != other.type) return false;
if (value == null) {
if (other.value != null) return false;
} else if (!value.equals(other.value)) return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + type;
result = prime * result + (value == null ? 0 : value.hashCode());
return result;
}
@Override
public String toString() {
return "FilterItemInfo{type=" + type + ", value=" + value + "}";
}
}
private static class UserItem {
private final UserKey key;
private final String name, screen_name;
public UserItem(UserKey key, String name, String screen_name) {
this.key = key;
this.name = name;
this.screen_name = screen_name;
}
}
}

View File

@ -1,412 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.content.res.ResourcesCompat;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.afollestad.appthemeengine.ATEActivity;
import com.afollestad.appthemeengine.Config;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
import com.mobeta.android.dslv.SimpleDragSortCursorAdapter;
import org.mariotaku.sqliteqb.library.Columns.Column;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.sqliteqb.library.RawItemArray;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.CustomTabEditorActivity;
import org.mariotaku.twidere.activity.SettingsActivity;
import org.mariotaku.twidere.model.CustomTabConfiguration;
import org.mariotaku.twidere.model.CustomTabConfiguration.CustomTabConfigurationComparator;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.provider.TwidereDataStore.Tabs;
import org.mariotaku.twidere.util.CustomTabUtils;
import org.mariotaku.twidere.util.DataStoreUtils;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.view.holder.TwoLineWithIconViewHolder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import static org.mariotaku.twidere.Constants.EXTRA_ARGUMENTS;
import static org.mariotaku.twidere.Constants.EXTRA_EXTRAS;
import static org.mariotaku.twidere.Constants.EXTRA_ICON;
import static org.mariotaku.twidere.Constants.EXTRA_ID;
import static org.mariotaku.twidere.Constants.EXTRA_NAME;
import static org.mariotaku.twidere.Constants.EXTRA_TYPE;
import static org.mariotaku.twidere.Constants.INTENT_ACTION_ADD_TAB;
import static org.mariotaku.twidere.Constants.INTENT_ACTION_EDIT_TAB;
import static org.mariotaku.twidere.Constants.REQUEST_ADD_TAB;
import static org.mariotaku.twidere.Constants.REQUEST_EDIT_TAB;
public class CustomTabsFragment extends BaseSupportFragment implements LoaderCallbacks<Cursor>,
MultiChoiceModeListener, OnItemClickListener {
private ContentResolver mResolver;
private CustomTabsAdapter mAdapter;
private DragSortListView mListView;
private View mEmptyView;
private View mListContainer, mProgressContainer;
private TextView mEmptyText;
private ImageView mEmptyIcon;
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
switch (item.getItemId()) {
case R.id.delete: {
final long[] itemIds = mListView.getCheckedItemIds();
final Expression where = Expression.in(new Column(Tabs._ID), new RawItemArray(itemIds));
mResolver.delete(Tabs.CONTENT_URI, where.getSQL(), null);
SettingsActivity.Companion.setShouldNotifyChange(getActivity());
break;
}
}
mode.finish();
return true;
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
mResolver = getContentResolver();
final View view = getView();
assert view != null;
final Context context = view.getContext();
mAdapter = new CustomTabsAdapter(context);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(this);
mListView.setOnItemClickListener(this);
mListView.setAdapter(mAdapter);
mListView.setEmptyView(mEmptyView);
mListView.setDropListener(new DropListener() {
@Override
public void drop(final int from, final int to) {
mAdapter.drop(from, to);
if (mListView.getChoiceMode() != AbsListView.CHOICE_MODE_NONE) {
mListView.moveCheckState(from, to);
}
saveTabPositions();
}
});
mEmptyText.setText(R.string.no_tab);
mEmptyIcon.setImageResource(R.drawable.ic_info_tab);
getLoaderManager().initLoader(0, null, this);
setListShown(false);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor c = mAdapter.getCursor();
c.moveToPosition(mAdapter.getCursorPosition(position));
final Intent intent = new Intent(INTENT_ACTION_EDIT_TAB);
intent.setClass(getActivity(), CustomTabEditorActivity.class);
intent.putExtra(EXTRA_ID, c.getLong(c.getColumnIndex(Tabs._ID)));
intent.putExtra(EXTRA_TYPE, c.getString(c.getColumnIndex(Tabs.TYPE)));
intent.putExtra(EXTRA_NAME, c.getString(c.getColumnIndex(Tabs.NAME)));
intent.putExtra(EXTRA_ICON, c.getString(c.getColumnIndex(Tabs.ICON)));
intent.putExtra(EXTRA_EXTRAS, c.getString(c.getColumnIndex(Tabs.EXTRAS)));
startActivityForResult(intent, REQUEST_EDIT_TAB);
}
private void setListShown(boolean shown) {
mListContainer.setVisibility(shown ? View.VISIBLE : View.GONE);
mProgressContainer.setVisibility(shown ? View.GONE : View.VISIBLE);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mListView = (DragSortListView) view.findViewById(android.R.id.list);
mEmptyView = view.findViewById(android.R.id.empty);
mEmptyIcon = (ImageView) view.findViewById(R.id.empty_icon);
mEmptyText = (TextView) view.findViewById(R.id.empty_text);
mListContainer = view.findViewById(R.id.list_container);
mProgressContainer = view.findViewById(R.id.progress_container);
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_ADD_TAB: {
if (resultCode == Activity.RESULT_OK) {
final ContentValues values = new ContentValues();
values.put(Tabs.NAME, data.getStringExtra(EXTRA_NAME));
values.put(Tabs.ICON, data.getStringExtra(EXTRA_ICON));
values.put(Tabs.TYPE, data.getStringExtra(EXTRA_TYPE));
values.put(Tabs.ARGUMENTS, data.getStringExtra(EXTRA_ARGUMENTS));
values.put(Tabs.EXTRAS, data.getStringExtra(EXTRA_EXTRAS));
values.put(Tabs.POSITION, mAdapter.getCount());
mResolver.insert(Tabs.CONTENT_URI, values);
SettingsActivity.Companion.setShouldNotifyChange(getActivity());
}
break;
}
case REQUEST_EDIT_TAB: {
if (resultCode == Activity.RESULT_OK && data.hasExtra(EXTRA_ID)) {
final ContentValues values = new ContentValues();
values.put(Tabs.NAME, data.getStringExtra(EXTRA_NAME));
values.put(Tabs.ICON, data.getStringExtra(EXTRA_ICON));
values.put(Tabs.EXTRAS, data.getStringExtra(EXTRA_EXTRAS));
final String where = Expression.equals(Tabs._ID, data.getLongExtra(EXTRA_ID, -1)).getSQL();
mResolver.update(Tabs.CONTENT_URI, values, where, null);
SettingsActivity.Companion.setShouldNotifyChange(getActivity());
}
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
mode.getMenuInflater().inflate(R.menu.action_multi_select_items, menu);
return true;
}
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
return new CursorLoader(getActivity(), Tabs.CONTENT_URI, Tabs.COLUMNS, null, null, Tabs.DEFAULT_SORT_ORDER);
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.menu_custom_tabs, menu);
final Resources res = getResources();
final FragmentActivity activity = getActivity();
final UserKey[] accountIds = DataStoreUtils.getAccountKeys(activity);
final MenuItem itemAdd = menu.findItem(R.id.add_submenu);
if (itemAdd != null && itemAdd.hasSubMenu()) {
final SubMenu subMenu = itemAdd.getSubMenu();
subMenu.clear();
final HashMap<String, CustomTabConfiguration> map = CustomTabUtils.getConfigurationMap();
final List<Entry<String, CustomTabConfiguration>> tabs = new ArrayList<>(
map.entrySet());
Collections.sort(tabs, CustomTabConfigurationComparator.SINGLETON);
for (final Entry<String, CustomTabConfiguration> entry : tabs) {
final String type = entry.getKey();
final CustomTabConfiguration conf = entry.getValue();
final boolean accountIdRequired = conf.getAccountRequirement() == CustomTabConfiguration.ACCOUNT_REQUIRED;
final Intent intent = new Intent(INTENT_ACTION_ADD_TAB);
intent.setClass(activity, CustomTabEditorActivity.class);
intent.putExtra(EXTRA_TYPE, type);
final MenuItem subItem = subMenu.add(conf.getDefaultTitle());
final boolean disabledByNoAccount = accountIdRequired && accountIds.length == 0;
final boolean disabledByDuplicateTab = conf.isSingleTab() && CustomTabUtils.isTabAdded(activity, type);
final boolean shouldDisable = disabledByDuplicateTab || disabledByNoAccount;
subItem.setVisible(!shouldDisable);
subItem.setEnabled(!shouldDisable);
final Drawable icon = ResourcesCompat.getDrawable(res, conf.getDefaultIcon(), null);
if (icon != null && activity instanceof ATEActivity) {
icon.mutate().setColorFilter(Config.textColorPrimary(activity,
((ATEActivity) activity).getATEKey()), Mode.SRC_ATOP);
}
subItem.setIcon(icon);
subItem.setIntent(intent);
}
}
}
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(R.layout.layout_draggable_list_with_empty_view, container, false);
}
@Override
public void onDestroyActionMode(final ActionMode mode) {
}
@Override
public void onItemCheckedStateChanged(final ActionMode mode, final int position, final long id,
final boolean checked) {
updateTitle(mode);
}
@Override
public void onLoaderReset(final Loader<Cursor> loader) {
mAdapter.changeCursor(null);
}
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) {
mAdapter.changeCursor(cursor);
setListShown(true);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
default: {
final Intent intent = item.getIntent();
if (intent == null) return false;
startActivityForResult(intent, REQUEST_ADD_TAB);
return true;
}
}
}
@Override
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
updateTitle(mode);
return true;
}
@Override
public void onStop() {
super.onStop();
}
private void saveTabPositions() {
final ArrayList<Integer> positions = mAdapter.getCursorPositions();
final Cursor c = mAdapter.getCursor();
if (positions != null && c != null && !c.isClosed()) {
final int idIdx = c.getColumnIndex(Tabs._ID);
for (int i = 0, j = positions.size(); i < j; i++) {
c.moveToPosition(positions.get(i));
final long id = c.getLong(idIdx);
final ContentValues values = new ContentValues();
values.put(Tabs.POSITION, i);
final String where = Expression.equals(Tabs._ID, id).getSQL();
mResolver.update(Tabs.CONTENT_URI, values, where, null);
}
}
SettingsActivity.Companion.setShouldNotifyChange(getActivity());
}
private void updateTitle(final ActionMode mode) {
if (mListView == null || mode == null || getActivity() == null) return;
final int count = mListView.getCheckedItemCount();
mode.setTitle(getResources().getQuantityString(R.plurals.Nitems_selected, count, count));
}
public static class CustomTabsAdapter extends SimpleDragSortCursorAdapter {
private final int mIconColor;
private CursorIndices mIndices;
public CustomTabsAdapter(final Context context) {
super(context, R.layout.list_item_custom_tab, null, new String[0], new int[0], 0);
mIconColor = ThemeUtils.getThemeForegroundColor(context);
}
@Override
public void bindView(final View view, final Context context, final Cursor cursor) {
super.bindView(view, context, cursor);
final TwoLineWithIconViewHolder holder = (TwoLineWithIconViewHolder) view.getTag();
final String type = cursor.getString(mIndices.type);
final String name = cursor.getString(mIndices.name);
final String iconKey = cursor.getString(mIndices.icon);
if (CustomTabUtils.isTabTypeValid(type)) {
final String typeName = CustomTabUtils.getTabTypeName(context, type);
holder.text1.setText(TextUtils.isEmpty(name) ? typeName : name);
holder.text1.setPaintFlags(holder.text1.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(typeName);
} else {
holder.text1.setText(name);
holder.text1.setPaintFlags(holder.text1.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
holder.text2.setText(R.string.invalid_tab);
}
final Drawable icon = CustomTabUtils.getTabIconDrawable(context, CustomTabUtils.getTabIconObject(iconKey));
holder.icon.setVisibility(View.VISIBLE);
if (icon != null) {
holder.icon.setImageDrawable(icon);
} else {
holder.icon.setImageResource(R.drawable.ic_action_list);
}
holder.icon.setColorFilter(mIconColor, Mode.SRC_ATOP);
}
@Override
public void changeCursor(final Cursor cursor) {
if (cursor != null) {
mIndices = new CursorIndices(cursor);
}
super.changeCursor(cursor);
}
@Override
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
final View view = super.newView(context, cursor, parent);
final Object tag = view.getTag();
if (!(tag instanceof TwoLineWithIconViewHolder)) {
final TwoLineWithIconViewHolder holder = new TwoLineWithIconViewHolder(view);
view.setTag(holder);
}
return view;
}
static class CursorIndices {
final int _id, name, icon, type, arguments;
CursorIndices(final Cursor mCursor) {
_id = mCursor.getColumnIndex(Tabs._ID);
icon = mCursor.getColumnIndex(Tabs.ICON);
name = mCursor.getColumnIndex(Tabs.NAME);
type = mCursor.getColumnIndex(Tabs.TYPE);
arguments = mCursor.getColumnIndex(Tabs.ARGUMENTS);
}
}
}
}

View File

@ -1,78 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AlertDialog;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
public class DestroyStatusDialogFragment extends BaseDialogFragment implements DialogInterface.OnClickListener {
public static final String FRAGMENT_TAG = "destroy_status";
@Override
public void onClick(final DialogInterface dialog, final int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
final ParcelableStatus status = getStatus();
final AsyncTwitterWrapper twitter = twitterWrapper;
if (status == null) return;
twitter.destroyStatusAsync(status.account_key, status.id);
break;
default:
break;
}
}
@NonNull
@Override
public Dialog onCreateDialog(final Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.destroy_status);
builder.setMessage(R.string.destroy_status_confirm_message);
builder.setPositiveButton(android.R.string.ok, this);
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
private ParcelableStatus getStatus() {
final Bundle args = getArguments();
if (!args.containsKey(EXTRA_STATUS)) return null;
return args.getParcelable(EXTRA_STATUS);
}
public static DestroyStatusDialogFragment show(final FragmentManager fm, final ParcelableStatus status) {
final Bundle args = new Bundle();
args.putParcelable(EXTRA_STATUS, status);
final DestroyStatusDialogFragment f = new DestroyStatusDialogFragment();
f.setArguments(args);
f.show(fm, FRAGMENT_TAG);
return f;
}
}

View File

@ -1,9 +0,0 @@
package org.mariotaku.twidere.fragment;
import android.support.v4.app.Fragment;
/**
* Created by mariotaku on 16/3/28.
*/
public class MessagesEntriesFragment extends Fragment {
}

View File

@ -0,0 +1,291 @@
package org.mariotaku.twidere.fragment
import android.app.Activity
import android.app.AlertDialog
import android.app.Dialog
import android.content.ContentValues
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.database.Cursor
import android.graphics.Color
import android.os.Bundle
import android.support.v4.app.LoaderManager.LoaderCallbacks
import android.support.v4.content.CursorLoader
import android.support.v4.content.Loader
import android.support.v4.util.SimpleArrayMap
import android.view.*
import android.view.ContextMenu.ContextMenuInfo
import android.widget.AbsListView
import android.widget.AdapterView
import android.widget.AdapterView.AdapterContextMenuInfo
import com.mobeta.android.dslv.DragSortListView.DropListener
import kotlinx.android.synthetic.main.layout_draggable_list_with_empty_view.*
import org.mariotaku.sqliteqb.library.ArgsArray
import org.mariotaku.sqliteqb.library.Columns
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.ColorPickerDialogActivity
import org.mariotaku.twidere.activity.SignInActivity
import org.mariotaku.twidere.adapter.AccountsAdapter
import org.mariotaku.twidere.annotation.Referral
import org.mariotaku.twidere.constant.SharedPreferenceConstants
import org.mariotaku.twidere.model.ParcelableAccount
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.TwidereCollectionUtils
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.collection.CompactHashSet
/**
* Created by mariotaku on 14/10/26.
*/
class AccountsManagerFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, DropListener, OnSharedPreferenceChangeListener, AdapterView.OnItemClickListener, AccountsAdapter.OnAccountToggleListener {
private var adapter: AccountsAdapter? = null
private var selectedAccount: ParcelableAccount? = null
private val activatedState = SimpleArrayMap<UserKey, Boolean>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
val activity = activity
preferences.registerOnSharedPreferenceChangeListener(this)
adapter = AccountsAdapter(activity)
Utils.configBaseAdapter(activity, adapter)
adapter!!.setSortEnabled(true)
adapter!!.setSwitchEnabled(true)
adapter!!.setOnAccountToggleListener(this)
listView.adapter = adapter
listView.isDragEnabled = true
listView.setDropListener(this)
listView.onItemClickListener = this
listView.setOnCreateContextMenuListener(this)
listView.emptyView = emptyView
emptyText.setText(R.string.no_account)
emptyIcon.setImageResource(R.drawable.ic_info_error_generic)
loaderManager.initLoader(0, null, this)
setListShown(false)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_SET_COLOR -> {
if (resultCode != Activity.RESULT_OK || data == null || selectedAccount == null)
return
val values = ContentValues()
values.put(Accounts.COLOR, data.getIntExtra(EXTRA_COLOR, Color.WHITE))
val where = Expression.equalsArgs(Accounts.ACCOUNT_KEY)
val whereArgs = arrayOf(selectedAccount!!.account_key.toString())
val cr = contentResolver
cr.update(Accounts.CONTENT_URI, values, where.sql, whereArgs)
return
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add_account -> {
val intent = Intent(INTENT_ACTION_TWITTER_LOGIN)
intent.setClass(activity, SignInActivity::class.java)
startActivity(intent)
}
}
return super.onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_accounts_manager, menu)
}
override fun onContextItemSelected(item: MenuItem?): Boolean {
val menuInfo = item!!.menuInfo
if (menuInfo !is AdapterContextMenuInfo) return false
val account = adapter!!.getAccount(menuInfo.position)
selectedAccount = account
if (account == null) return false
when (item.itemId) {
R.id.set_color -> {
val intent = Intent(activity, ColorPickerDialogActivity::class.java)
intent.putExtra(EXTRA_COLOR, account.color)
intent.putExtra(EXTRA_ALPHA_SLIDER, false)
startActivityForResult(intent, REQUEST_SET_COLOR)
}
R.id.delete -> {
val f = AccountDeletionDialogFragment()
val args = Bundle()
args.putLong(EXTRA_ID, account.id)
f.arguments = args
f.show(childFragmentManager, FRAGMENT_TAG_ACCOUNT_DELETION)
}
}
return false
}
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val context = context ?: return
val account = adapter!!.getAccount(position)
if (account!!.account_user != null) {
IntentUtils.openUserProfile(context, account.account_user!!, null,
preferences.getBoolean(SharedPreferenceConstants.KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE)
} else {
IntentUtils.openUserProfile(context, account.account_key, account.account_key,
account.screen_name, null, preferences.getBoolean(SharedPreferenceConstants.KEY_NEW_DOCUMENT_API),
Referral.SELF_PROFILE)
}
}
override fun onStop() {
super.onStop()
saveActivatedState()
}
private fun saveActivatedState() {
val trueIds = CompactHashSet<UserKey>()
val falseIds = CompactHashSet<UserKey>()
var i = 0
val j = activatedState.size()
while (i < j) {
if (activatedState.valueAt(i)) {
trueIds.add(activatedState.keyAt(i))
} else {
falseIds.add(activatedState.keyAt(i))
}
i++
}
val cr = contentResolver
val values = ContentValues()
values.put(Accounts.IS_ACTIVATED, true)
var where = Expression.`in`(Columns.Column(Accounts.ACCOUNT_KEY), ArgsArray(trueIds.size))
var whereArgs = TwidereCollectionUtils.toStringArray(trueIds)
cr.update(Accounts.CONTENT_URI, values, where.sql, whereArgs)
values.put(Accounts.IS_ACTIVATED, false)
where = Expression.`in`(Columns.Column(Accounts.ACCOUNT_KEY), ArgsArray(falseIds.size))
whereArgs = TwidereCollectionUtils.toStringArray(falseIds)
cr.update(Accounts.CONTENT_URI, values, where.sql, whereArgs)
}
override fun onAccountToggle(accountId: UserKey, state: Boolean) {
activatedState.put(accountId, state)
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo) {
if (menuInfo !is AdapterContextMenuInfo) return
val account = adapter!!.getAccount(menuInfo.position)
menu.setHeaderTitle(account!!.name)
val inflater = MenuInflater(v.context)
inflater.inflate(R.menu.action_manager_account, menu)
}
override fun onDestroyView() {
preferences.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroyView()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.layout_draggable_list_with_empty_view, container, false)
}
private fun setListShown(shown: Boolean) {
listContainer.visibility = if (shown) View.VISIBLE else View.GONE
progressContainer.visibility = if (shown) View.GONE else View.VISIBLE
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
val uri = Accounts.CONTENT_URI
return CursorLoader(activity, uri, Accounts.COLUMNS, null, null, Accounts.SORT_POSITION)
}
override fun onLoadFinished(loader: Loader<Cursor?>, cursor: Cursor?) {
setListShown(true)
adapter!!.changeCursor(cursor)
}
override fun onLoaderReset(loader: Loader<Cursor?>) {
adapter!!.changeCursor(null)
}
override fun drop(from: Int, to: Int) {
adapter!!.drop(from, to)
if (listView.choiceMode != AbsListView.CHOICE_MODE_NONE) {
listView.moveCheckState(from, to)
}
saveAccountPositions()
}
private fun saveAccountPositions() {
val cr = contentResolver
val positions = adapter!!.cursorPositions
val c = adapter!!.cursor
if (positions != null && c != null && !c.isClosed) {
val idIdx = c.getColumnIndex(Accounts._ID)
var i = 0
val j = positions.size
while (i < j) {
c.moveToPosition(positions[i])
val id = c.getLong(idIdx)
val values = ContentValues()
values.put(Accounts.SORT_POSITION, i)
val where = Expression.equals(Accounts._ID, id)
cr.update(Accounts.CONTENT_URI, values, where.sql, null)
i++
}
}
}
override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) {
if (SharedPreferenceConstants.KEY_DEFAULT_ACCOUNT_KEY == key) {
updateDefaultAccount()
}
}
private fun updateDefaultAccount() {
adapter!!.notifyDataSetChanged()
}
class AccountDeletionDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
val id = arguments.getLong(EXTRA_ID)
val resolver = contentResolver
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val where = Expression.equalsArgs(Accounts._ID).sql
val whereArgs = arrayOf(id.toString())
resolver.delete(Accounts.CONTENT_URI, where, whereArgs)
// Also delete tweets related to the account we previously
// deleted.
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
resolver.delete(Inbox.CONTENT_URI, where, whereArgs)
resolver.delete(Outbox.CONTENT_URI, where, whereArgs)
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = context
val builder = AlertDialog.Builder(context)
builder.setNegativeButton(android.R.string.cancel, null)
builder.setPositiveButton(android.R.string.ok, this)
builder.setTitle(R.string.account_delete_confirm_title)
builder.setMessage(R.string.account_delete_confirm_message)
return builder.create()
}
}
companion object {
private val FRAGMENT_TAG_ACCOUNT_DELETION = "account_deletion"
}
}

View File

@ -0,0 +1,215 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.app.Dialog
import android.content.ContentValues
import android.os.Bundle
import android.support.v4.app.FragmentManager
import android.support.v7.app.AlertDialog
import com.twitter.Extractor
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_NAME_FIRST
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.ParcelableUserMention
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
import org.mariotaku.twidere.util.ContentValuesCreator
import org.mariotaku.twidere.util.HtmlEscapeHelper
import org.mariotaku.twidere.util.ParseUtils
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.*
class AddStatusFilterDialogFragment : BaseDialogFragment() {
private val extractor = Extractor()
private var filterItems: Array<FilterItemInfo>? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(context)
filterItems = filterItemsInfo
val entries = arrayOfNulls<String>(filterItems!!.size)
val nameFirst = preferences.getBoolean(KEY_NAME_FIRST)
run {
var i = 0
val j = entries.size
while (i < j) {
val info = filterItems!![i]
when (info.type) {
FilterItemInfo.FILTER_TYPE_USER -> {
entries[i] = getString(R.string.user_filter_name, getName(userColorNameManager,
info.value, nameFirst))
}
FilterItemInfo.FILTER_TYPE_KEYWORD -> {
entries[i] = getString(R.string.keyword_filter_name, getName(userColorNameManager,
info.value, nameFirst))
}
FilterItemInfo.FILTER_TYPE_SOURCE -> {
entries[i] = getString(R.string.source_filter_name, getName(userColorNameManager,
info.value, nameFirst))
}
}
i++
}
}
builder.setTitle(R.string.add_to_filter)
builder.setMultiChoiceItems(entries, null, null)
builder.setPositiveButton(android.R.string.ok) { dialog, which ->
val alertDialog = dialog as AlertDialog
val checkPositions = alertDialog.listView.checkedItemPositions
val userKeys = HashSet<UserKey>()
val keywords = HashSet<String>()
val sources = HashSet<String>()
val userValues = ArrayList<ContentValues>()
val keywordValues = ArrayList<ContentValues>()
val sourceValues = ArrayList<ContentValues>()
var i = 0
val j = checkPositions.size()
while (i < j) {
if (!checkPositions.valueAt(i)) {
i++
continue
}
val info = filterItems!![checkPositions.keyAt(i)]
val value = info.value
if (value is ParcelableUserMention) {
userKeys.add(value.key)
userValues.add(ContentValuesCreator.createFilteredUser(value))
} else if (value is UserItem) {
userKeys.add(value.key)
userValues.add(createFilteredUser(value))
} else if (info.type == FilterItemInfo.FILTER_TYPE_KEYWORD) {
val keyword = ParseUtils.parseString(value)
keywords.add(keyword)
val values = ContentValues()
values.put(Filters.Keywords.VALUE, "#" + keyword)
keywordValues.add(values)
} else if (info.type == FilterItemInfo.FILTER_TYPE_SOURCE) {
val source = ParseUtils.parseString(value)
sources.add(source)
val values = ContentValues()
values.put(Filters.Sources.VALUE, source)
sourceValues.add(values)
}
i++
}
val resolver = contentResolver
ContentResolverUtils.bulkDelete(resolver, Filters.Users.CONTENT_URI, Filters.Users.USER_KEY, userKeys, null)
ContentResolverUtils.bulkDelete(resolver, Filters.Keywords.CONTENT_URI, Filters.Keywords.VALUE, keywords, null)
ContentResolverUtils.bulkDelete(resolver, Filters.Sources.CONTENT_URI, Filters.Sources.VALUE, sources, null)
ContentResolverUtils.bulkInsert(resolver, Filters.Users.CONTENT_URI, userValues)
ContentResolverUtils.bulkInsert(resolver, Filters.Keywords.CONTENT_URI, keywordValues)
ContentResolverUtils.bulkInsert(resolver, Filters.Sources.CONTENT_URI, sourceValues)
}
builder.setNegativeButton(android.R.string.cancel, null)
return builder.create()
}
private val filterItemsInfo: Array<FilterItemInfo>
get() {
val args = arguments
if (args == null || !args.containsKey(EXTRA_STATUS)) return emptyArray()
val status = args.getParcelable<ParcelableStatus>(EXTRA_STATUS) ?: return emptyArray()
val list = ArrayList<FilterItemInfo>()
if (status.is_retweet && status.retweeted_by_user_key != null) {
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER,
UserItem(status.retweeted_by_user_key!!, status.retweeted_by_user_name,
status.retweeted_by_user_screen_name)))
}
if (status.is_quote && status.quoted_user_key != null) {
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER,
UserItem(status.quoted_user_key!!, status.quoted_user_name,
status.quoted_user_screen_name)))
}
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER, UserItem(status.user_key,
status.user_name, status.user_screen_name)))
val mentions = status.mentions
if (mentions != null) {
for (mention in mentions) {
if (mention.key != status.user_key) {
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_USER, mention))
}
}
}
val hashtags = HashSet<String>()
hashtags.addAll(extractor.extractHashtags(status.text_plain))
for (hashtag in hashtags) {
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_KEYWORD, hashtag))
}
val source = HtmlEscapeHelper.toPlainText(status.source)
list.add(FilterItemInfo(FilterItemInfo.FILTER_TYPE_SOURCE, source))
return list.toTypedArray()
}
private fun getName(manager: UserColorNameManager, value: Any, nameFirst: Boolean): String {
if (value is ParcelableUserMention) {
return manager.getDisplayName(value.key, value.name, value.screen_name, nameFirst)
} else if (value is UserItem) {
return manager.getDisplayName(value.key, value.name, value.screen_name, nameFirst)
} else
return ParseUtils.parseString(value)
}
internal data class FilterItemInfo(
val type: Int,
val value: Any
) {
companion object {
internal const val FILTER_TYPE_USER = 1
internal const val FILTER_TYPE_KEYWORD = 2
internal const val FILTER_TYPE_SOURCE = 3
}
}
internal data class UserItem(
val key: UserKey,
val name: String,
val screen_name: String
)
companion object {
val FRAGMENT_TAG = "add_status_filter"
private fun createFilteredUser(item: UserItem): ContentValues {
val values = ContentValues()
values.put(Filters.Users.USER_KEY, item.key.toString())
values.put(Filters.Users.NAME, item.name)
values.put(Filters.Users.SCREEN_NAME, item.screen_name)
return values
}
fun show(fm: FragmentManager, status: ParcelableStatus): AddStatusFilterDialogFragment {
val args = Bundle()
args.putParcelable(EXTRA_STATUS, status)
val f = AddStatusFilterDialogFragment()
f.arguments = args
f.show(fm, FRAGMENT_TAG)
return f
}
}
}

View File

@ -0,0 +1,335 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.app.Activity
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.graphics.Paint
import android.graphics.PorterDuff.Mode
import android.os.Bundle
import android.support.v4.app.LoaderManager.LoaderCallbacks
import android.support.v4.content.CursorLoader
import android.support.v4.content.Loader
import android.support.v4.content.res.ResourcesCompat
import android.text.TextUtils
import android.view.*
import android.widget.AbsListView
import android.widget.AbsListView.MultiChoiceModeListener
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.ListView
import com.afollestad.appthemeengine.ATEActivity
import com.afollestad.appthemeengine.Config
import com.mobeta.android.dslv.SimpleDragSortCursorAdapter
import kotlinx.android.synthetic.main.layout_draggable_list_with_empty_view.*
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.sqliteqb.library.RawItemArray
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.activity.CustomTabEditorActivity
import org.mariotaku.twidere.activity.SettingsActivity
import org.mariotaku.twidere.model.CustomTabConfiguration
import org.mariotaku.twidere.model.CustomTabConfiguration.CustomTabConfigurationComparator
import org.mariotaku.twidere.provider.TwidereDataStore.Tabs
import org.mariotaku.twidere.util.CustomTabUtils
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.ThemeUtils
import org.mariotaku.twidere.view.holder.TwoLineWithIconViewHolder
import java.util.*
class CustomTabsFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, MultiChoiceModeListener, OnItemClickListener {
private var adapter: CustomTabsAdapter? = null
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
when (item.itemId) {
R.id.delete -> {
val itemIds = listView.checkedItemIds
val where = Expression.`in`(Column(Tabs._ID), RawItemArray(itemIds))
contentResolver.delete(Tabs.CONTENT_URI, where.sql, null)
SettingsActivity.setShouldNotifyChange(activity)
}
}
mode.finish()
return true
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
adapter = CustomTabsAdapter(context)
listView.choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL
listView.setMultiChoiceModeListener(this)
listView.onItemClickListener = this
listView.adapter = adapter
listView.emptyView = emptyView
listView.setDropListener { from, to ->
adapter!!.drop(from, to)
if (listView.choiceMode != AbsListView.CHOICE_MODE_NONE) {
listView.moveCheckState(from, to)
}
saveTabPositions()
}
emptyText.setText(R.string.no_tab)
emptyIcon.setImageResource(R.drawable.ic_info_tab)
loaderManager.initLoader(0, null, this)
setListShown(false)
}
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val c = adapter!!.cursor
c.moveToPosition(adapter!!.getCursorPosition(position))
val intent = Intent(INTENT_ACTION_EDIT_TAB)
intent.setClass(activity, CustomTabEditorActivity::class.java)
intent.putExtra(EXTRA_ID, c.getLong(c.getColumnIndex(Tabs._ID)))
intent.putExtra(EXTRA_TYPE, c.getString(c.getColumnIndex(Tabs.TYPE)))
intent.putExtra(EXTRA_NAME, c.getString(c.getColumnIndex(Tabs.NAME)))
intent.putExtra(EXTRA_ICON, c.getString(c.getColumnIndex(Tabs.ICON)))
intent.putExtra(EXTRA_EXTRAS, c.getString(c.getColumnIndex(Tabs.EXTRAS)))
startActivityForResult(intent, REQUEST_EDIT_TAB)
}
private fun setListShown(shown: Boolean) {
listContainer.visibility = if (shown) View.VISIBLE else View.GONE
progressContainer.visibility = if (shown) View.GONE else View.VISIBLE
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_ADD_TAB -> {
if (resultCode == Activity.RESULT_OK) {
val values = ContentValues()
values.put(Tabs.NAME, data!!.getStringExtra(EXTRA_NAME))
values.put(Tabs.ICON, data.getStringExtra(EXTRA_ICON))
values.put(Tabs.TYPE, data.getStringExtra(EXTRA_TYPE))
values.put(Tabs.ARGUMENTS, data.getStringExtra(EXTRA_ARGUMENTS))
values.put(Tabs.EXTRAS, data.getStringExtra(EXTRA_EXTRAS))
values.put(Tabs.POSITION, adapter!!.count)
contentResolver.insert(Tabs.CONTENT_URI, values)
SettingsActivity.setShouldNotifyChange(activity)
}
}
REQUEST_EDIT_TAB -> {
if (resultCode == Activity.RESULT_OK && data!!.hasExtra(EXTRA_ID)) {
val values = ContentValues()
values.put(Tabs.NAME, data.getStringExtra(EXTRA_NAME))
values.put(Tabs.ICON, data.getStringExtra(EXTRA_ICON))
values.put(Tabs.EXTRAS, data.getStringExtra(EXTRA_EXTRAS))
val where = Expression.equals(Tabs._ID, data.getLongExtra(EXTRA_ID, -1)).sql
contentResolver.update(Tabs.CONTENT_URI, values, where, null)
SettingsActivity.setShouldNotifyChange(activity)
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.action_multi_select_items, menu)
return true
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
return CursorLoader(activity, Tabs.CONTENT_URI, Tabs.COLUMNS, null, null, Tabs.DEFAULT_SORT_ORDER)
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater!!.inflate(R.menu.menu_custom_tabs, menu)
val res = resources
val activity = activity
val accountIds = DataStoreUtils.getAccountKeys(activity)
val itemAdd = menu!!.findItem(R.id.add_submenu)
if (itemAdd != null && itemAdd.hasSubMenu()) {
val subMenu = itemAdd.subMenu
subMenu.clear()
val map = CustomTabUtils.getConfigurationMap()
val tabs = ArrayList(
map.entries)
Collections.sort(tabs, CustomTabConfigurationComparator.SINGLETON)
for ((type, conf) in tabs) {
val accountIdRequired = conf.accountRequirement == CustomTabConfiguration.ACCOUNT_REQUIRED
val intent = Intent(INTENT_ACTION_ADD_TAB)
intent.setClass(activity, CustomTabEditorActivity::class.java)
intent.putExtra(EXTRA_TYPE, type)
val subItem = subMenu.add(conf.defaultTitle)
val disabledByNoAccount = accountIdRequired && accountIds.size == 0
val disabledByDuplicateTab = conf.isSingleTab && CustomTabUtils.isTabAdded(activity, type)
val shouldDisable = disabledByDuplicateTab || disabledByNoAccount
subItem.isVisible = !shouldDisable
subItem.isEnabled = !shouldDisable
val icon = ResourcesCompat.getDrawable(res, conf.defaultIcon, null)
if (icon != null && activity is ATEActivity) {
icon.mutate().setColorFilter(Config.textColorPrimary(activity,
activity.ateKey), Mode.SRC_ATOP)
}
subItem.icon = icon
subItem.intent = intent
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.layout_draggable_list_with_empty_view, container, false)
}
override fun onDestroyActionMode(mode: ActionMode) {
}
override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long,
checked: Boolean) {
updateTitle(mode)
}
override fun onLoaderReset(loader: Loader<Cursor?>) {
adapter!!.changeCursor(null)
}
override fun onLoadFinished(loader: Loader<Cursor?>, cursor: Cursor?) {
adapter!!.changeCursor(cursor)
setListShown(true)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
else -> {
val intent = item.intent ?: return false
startActivityForResult(intent, REQUEST_ADD_TAB)
return true
}
}
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
updateTitle(mode)
return true
}
override fun onStop() {
super.onStop()
}
private fun saveTabPositions() {
val positions = adapter!!.cursorPositions
val c = adapter!!.cursor
if (positions != null && c != null && !c.isClosed) {
val idIdx = c.getColumnIndex(Tabs._ID)
var i = 0
val j = positions.size
while (i < j) {
c.moveToPosition(positions[i])
val id = c.getLong(idIdx)
val values = ContentValues()
values.put(Tabs.POSITION, i)
val where = Expression.equals(Tabs._ID, id).sql
contentResolver.update(Tabs.CONTENT_URI, values, where, null)
i++
}
}
SettingsActivity.setShouldNotifyChange(activity)
}
private fun updateTitle(mode: ActionMode?) {
if (listView == null || mode == null || activity == null) return
val count = listView.checkedItemCount
mode.title = resources.getQuantityString(R.plurals.Nitems_selected, count, count)
}
class CustomTabsAdapter(context: Context) : SimpleDragSortCursorAdapter(context, R.layout.list_item_custom_tab, null, arrayOfNulls<String>(0), IntArray(0), 0) {
private val mIconColor: Int
private var indices: CursorIndices? = null
init {
mIconColor = ThemeUtils.getThemeForegroundColor(context)
}
override fun bindView(view: View, context: Context?, cursor: Cursor) {
super.bindView(view, context, cursor)
val holder = view.tag as TwoLineWithIconViewHolder
val indices = indices!!
val type = cursor.getString(indices.type)
val name = cursor.getString(indices.name)
val iconKey = cursor.getString(indices.icon)
if (CustomTabUtils.isTabTypeValid(type)) {
val typeName = CustomTabUtils.getTabTypeName(context, type)
holder.text1.text = if (TextUtils.isEmpty(name)) typeName else name
holder.text1.paintFlags = holder.text1.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
holder.text2.visibility = View.VISIBLE
holder.text2.text = typeName
} else {
holder.text1.text = name
holder.text1.paintFlags = holder.text1.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
holder.text2.setText(R.string.invalid_tab)
}
val icon = CustomTabUtils.getTabIconDrawable(context, CustomTabUtils.getTabIconObject(iconKey))
holder.icon.visibility = View.VISIBLE
if (icon != null) {
holder.icon.setImageDrawable(icon)
} else {
holder.icon.setImageResource(R.drawable.ic_action_list)
}
holder.icon.setColorFilter(mIconColor, Mode.SRC_ATOP)
}
override fun changeCursor(cursor: Cursor?) {
if (cursor != null) {
indices = CursorIndices(cursor)
}
super.changeCursor(cursor)
}
override fun newView(context: Context?, cursor: Cursor?, parent: ViewGroup): View {
val view = super.newView(context, cursor, parent)
val tag = view.tag
if (tag !is TwoLineWithIconViewHolder) {
val holder = TwoLineWithIconViewHolder(view)
view.tag = holder
}
return view
}
internal class CursorIndices(cursor: Cursor) {
val _id: Int
val name: Int
val icon: Int
val type: Int
val arguments: Int
init {
_id = cursor.getColumnIndex(Tabs._ID)
icon = cursor.getColumnIndex(Tabs.ICON)
name = cursor.getColumnIndex(Tabs.NAME)
type = cursor.getColumnIndex(Tabs.TYPE)
arguments = cursor.getColumnIndex(Tabs.ARGUMENTS)
}
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.support.v4.app.FragmentManager
import android.support.v7.app.AlertDialog
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS
import org.mariotaku.twidere.model.ParcelableStatus
class DestroyStatusDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val status = status ?: return
twitterWrapper.destroyStatusAsync(status.account_key, status.id)
}
else -> {
}
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = activity
val builder = AlertDialog.Builder(context)
builder.setTitle(R.string.destroy_status)
builder.setMessage(R.string.destroy_status_confirm_message)
builder.setPositiveButton(android.R.string.ok, this)
builder.setNegativeButton(android.R.string.cancel, null)
return builder.create()
}
private val status: ParcelableStatus?
get() {
val args = arguments
if (!args.containsKey(EXTRA_STATUS)) return null
return args.getParcelable<ParcelableStatus>(EXTRA_STATUS)
}
companion object {
val FRAGMENT_TAG = "destroy_status"
fun show(fm: FragmentManager, status: ParcelableStatus): DestroyStatusDialogFragment {
val args = Bundle()
args.putParcelable(EXTRA_STATUS, status)
val f = DestroyStatusDialogFragment()
f.arguments = args
f.show(fm, FRAGMENT_TAG)
return f
}
}
}

View File

@ -35,9 +35,9 @@ import android.support.v4.content.CursorLoader
import android.support.v4.content.Loader
import android.support.v4.view.ViewCompat
import android.support.v7.app.ActionBar
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.FixedLinearLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.PopupMenu
import android.support.v7.widget.RecyclerView
import android.text.Editable
import android.text.TextUtils
@ -50,7 +50,7 @@ import android.widget.AdapterView.OnItemSelectedListener
import android.widget.Toast
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.fragment_messages_conversation.*
import kotlinx.android.synthetic.main.layout_actionbar_message_user_picker.*
import kotlinx.android.synthetic.main.layout_actionbar_message_user_picker.view.*
import me.uucky.colorpicker.internal.EffectViewHelper
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
@ -85,15 +85,15 @@ import org.mariotaku.twidere.view.ExtendedRecyclerView
import java.util.*
import javax.inject.Inject
class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnClickListener, OnItemSelectedListener, PopupMenu.OnMenuItemClickListener, KeyboardShortcutCallback, TakeAllKeyboardShortcut {
class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Cursor?>, OnClickListener,
OnItemSelectedListener, KeyboardShortcutCallback, TakeAllKeyboardShortcut {
// Callbacks and listeners
private val searchLoadersCallback = object : LoaderCallbacks<List<ParcelableUser>> {
override fun onCreateLoader(id: Int, args: Bundle): Loader<List<ParcelableUser>> {
usersSearchList!!.visibility = View.GONE
usersSearchEmpty!!.visibility = View.GONE
usersSearchProgress!!.visibility = View.VISIBLE
usersSearchList.visibility = View.GONE
usersSearchEmpty.visibility = View.GONE
usersSearchProgress.visibility = View.VISIBLE
val accountKey = args.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
val query = args.getString(EXTRA_QUERY)
val fromCache = args.getBoolean(EXTRA_FROM_CACHE)
@ -103,10 +103,10 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
override fun onLoadFinished(loader: Loader<List<ParcelableUser>>, data: List<ParcelableUser>?) {
usersSearchList!!.visibility = View.VISIBLE
usersSearchProgress!!.visibility = View.GONE
usersSearchEmpty!!.visibility = if (data == null || data.isEmpty()) View.GONE else View.VISIBLE
mUsersSearchAdapter!!.setData(data, true)
usersSearchList.visibility = View.VISIBLE
usersSearchProgress.visibility = View.GONE
usersSearchEmpty.visibility = if (data == null || data.isEmpty()) View.GONE else View.VISIBLE
usersSearchAdapter!!.setData(data, true)
updateEmptyText()
}
@ -121,12 +121,11 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
// Adapters
private var adapter: MessageConversationAdapter? = null
private var mUsersSearchAdapter: SimpleParcelableUsersAdapter? = null
private var usersSearchAdapter: SimpleParcelableUsersAdapter? = null
// Data fields
private var searchUsersLoaderInitialized: Boolean = false
private var navigateBackPressed: Boolean = false
private val selectedDirectMessage: ParcelableDirectMessage? = null
private var loaderInitialized: Boolean = false
private var imageUri: String? = null
private var account: ParcelableCredentials? = null
@ -136,6 +135,9 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
private val backTimeoutRunnable = Runnable { navigateBackPressed = false }
private val actionBarCustomView: View
get() = (activity as AppCompatActivity).supportActionBar!!.customView
@Subscribe
fun notifyTaskStateChanged(event: TaskStateChangedEvent) {
updateRefreshState()
@ -163,10 +165,8 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
val activity = activity as BaseActivity
messageDrafts = activity.getSharedPreferences(MESSAGE_DRAFTS_PREFERENCES_NAME, Context.MODE_PRIVATE)
val view = view!!
val viewContext = view.context
setHasOptionsMenu(true)
val actionBar = activity.supportActionBar ?: throw NullPointerException()
val actionBar = activity.supportActionBar!!
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
ActionBar.DISPLAY_SHOW_TITLE or ActionBar.DISPLAY_SHOW_CUSTOM)
actionBar.setCustomView(R.layout.layout_actionbar_message_user_picker)
@ -175,11 +175,11 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
actionBar.themedContext, R.layout.spinner_item_account_icon)
accountsSpinnerAdapter.setDropDownViewResource(R.layout.list_item_user)
accountsSpinnerAdapter.addAll(accounts)
accountSpinner.adapter = accountsSpinnerAdapter
accountSpinner.onItemSelectedListener = this
queryButton.setOnClickListener(this)
actionBarCustomView.accountSpinner.adapter = accountsSpinnerAdapter
actionBarCustomView.accountSpinner.onItemSelectedListener = this
actionBarCustomView.queryButton.setOnClickListener(this)
adapter = MessageConversationAdapter(activity)
val layoutManager = FixedLinearLayoutManager(viewContext)
val layoutManager = FixedLinearLayoutManager(context)
layoutManager.orientation = LinearLayoutManager.VERTICAL
layoutManager.stackFromEnd = true
recyclerView.layoutManager = layoutManager
@ -197,24 +197,24 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
scrollListener = PanelShowHideListener(effectHelper)
inputPanelShadowCompat!!.visibility = if (useOutline) View.GONE else View.VISIBLE
inputPanelShadowCompat.visibility = if (useOutline) View.GONE else View.VISIBLE
ViewCompat.setAlpha(inputPanelShadowCompat, 0f)
mUsersSearchAdapter = SimpleParcelableUsersAdapter(activity)
usersSearchList!!.adapter = mUsersSearchAdapter
usersSearchList!!.emptyView = usersSearchEmpty
usersSearchList!!.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
val account = accountSpinner.selectedItem as ParcelableCredentials
showConversation(account, mUsersSearchAdapter!!.getItem(position))
usersSearchAdapter = SimpleParcelableUsersAdapter(activity)
usersSearchList.adapter = usersSearchAdapter
usersSearchList.emptyView = usersSearchEmpty
usersSearchList.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
val account = actionBarCustomView.accountSpinner.selectedItem as ParcelableCredentials
showConversation(account, usersSearchAdapter!!.getItem(position))
updateRecipientInfo()
}
setupEditQuery()
setupEditText()
send!!.setOnClickListener(this)
addImage!!.setOnClickListener(this)
send!!.isEnabled = false
sendMessage.setOnClickListener(this)
addImage.setOnClickListener(this)
sendMessage.isEnabled = false
if (savedInstanceState != null) {
val account = savedInstanceState.getParcelable<ParcelableCredentials>(EXTRA_ACCOUNT)
val recipient = savedInstanceState.getParcelable<ParcelableUser>(EXTRA_USER)
@ -237,7 +237,7 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
val accountPos = accountsSpinnerAdapter.findPositionByKey(accountKey)
if (accountPos >= 0) {
accountSpinner.setSelection(accountPos)
actionBarCustomView.accountSpinner.setSelection(accountPos)
}
val userId = args.getString(EXTRA_RECIPIENT_ID)
if (accountPos >= 0) {
@ -263,11 +263,11 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
editText.setSelection(editText.length())
val isValid = account != null && recipient != null
conversationContainer!!.visibility = if (isValid) View.VISIBLE else View.GONE
recipientSelectorContainer!!.visibility = if (isValid) View.GONE else View.VISIBLE
conversationContainer.visibility = if (isValid) View.VISIBLE else View.GONE
recipientSelectorContainer.visibility = if (isValid) View.GONE else View.VISIBLE
usersSearchList!!.visibility = View.GONE
usersSearchProgress!!.visibility = View.GONE
usersSearchList.visibility = View.GONE
usersSearchProgress.visibility = View.GONE
registerForContextMenu(recyclerView)
@ -360,23 +360,23 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
override fun onClick(view: View) {
when (view.id) {
R.id.send -> {
when (view) {
sendMessage -> {
sendDirectMessage()
}
R.id.addImage -> {
addImage -> {
val intent = ThemedImagePickerActivity.withThemed(activity).build()
startActivityForResult(intent, REQUEST_PICK_IMAGE)
}
R.id.queryButton -> {
val account = accountSpinner.selectedItem as ParcelableCredentials
searchUsers(account.account_key, ParseUtils.parseString(editUserQuery!!.text), false)
actionBarCustomView.queryButton -> {
val account = actionBarCustomView.accountSpinner.selectedItem as ParcelableCredentials
searchUsers(account.account_key, ParseUtils.parseString(actionBarCustomView.editUserQuery.text), false)
}
}
}
override fun onItemSelected(parent: AdapterView<*>, view: View, pos: Int, id: Long) {
val account = accountSpinner.selectedItem as ParcelableCredentials?
val account = actionBarCustomView.accountSpinner.selectedItem as ParcelableCredentials?
if (account != null) {
this.account = account
updateRecipientInfo()
@ -392,24 +392,6 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
adapter!!.setCursor(null)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
val message = selectedDirectMessage
if (message != null) {
when (item.itemId) {
R.id.delete -> {
twitterWrapper.destroyDirectMessageAsync(message.account_key, message.id)
}
R.id.copy -> {
if (ClipboardUtils.setText(activity, message.text_plain)) {
Utils.showOkMessage(activity, R.string.text_copied, false)
}
}
else -> return false
}
}
return true
}
override fun onLoadFinished(loader: Loader<Cursor?>, cursor: Cursor?) {
adapter!!.setCursor(cursor)
}
@ -450,7 +432,7 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
val action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event, metaState)
if (ACTION_NAVIGATION_BACK == action) {
val showingConversation = isShowingConversation
val editText = if (showingConversation) editText else editUserQuery
val editText = if (showingConversation) editText else actionBarCustomView.editUserQuery
val textChanged = if (showingConversation) textChanged else queryTextChanged
if (editText.length() == 0 && !textChanged) {
val activity = activity
@ -505,9 +487,9 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
private fun updateAccount() {
if (account == null) return
if (Utils.isOfficialCredentials(context, account!!)) {
addImage!!.visibility = View.VISIBLE
addImage.visibility = View.VISIBLE
} else {
addImage!!.visibility = View.GONE
addImage.visibility = View.GONE
}
}
@ -576,7 +558,7 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
private fun setupEditQuery() {
val queryEnterHandler = EditTextEnterHandler.attach(editUserQuery!!, object : EnterListener {
val queryEnterHandler = EditTextEnterHandler.attach(actionBarCustomView.editUserQuery, object : EnterListener {
override fun shouldCallListener(): Boolean {
val activity = activity
if (activity !is BaseActivity) return false
@ -587,9 +569,9 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
val activity = activity
if (activity !is BaseActivity) return false
if (activity.keyMetaState != 0) return false
val account = accountSpinner.selectedItem as ParcelableCredentials ?: return false
val account = actionBarCustomView.accountSpinner.selectedItem as ParcelableCredentials ?: return false
editText.setAccountKey(account.account_key)
searchUsers(account.account_key, ParseUtils.parseString(editUserQuery!!.text), false)
searchUsers(account.account_key, ParseUtils.parseString(actionBarCustomView.editUserQuery.text), false)
return true
}
}, true)
@ -602,12 +584,12 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
override fun afterTextChanged(s: Editable) {
val account = (accountSpinner.selectedItem ?: return) as ParcelableCredentials
val account = (actionBarCustomView.accountSpinner.selectedItem ?: return) as ParcelableCredentials
editText.setAccountKey(account.account_key)
searchUsers(account.account_key, ParseUtils.parseString(s), true)
}
})
editUserQuery!!.addTextChangedListener(object : TextWatcher {
actionBarCustomView.editUserQuery.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
@ -645,8 +627,8 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (send == null || s == null) return
send!!.isEnabled = validator.isValidDirectMessage(s.toString())
if (s == null) return
sendMessage.isEnabled = validator.isValidDirectMessage(s.toString())
}
})
}
@ -659,7 +641,7 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
}
private fun updateAddImageButton() {
addImage!!.isActivated = imageUri != null
addImage.isActivated = imageUri != null
}
// @Override
@ -670,7 +652,7 @@ class MessagesConversationFragment : BaseSupportFragment(), LoaderCallbacks<Curs
// }
private fun updateEmptyText() {
val noQuery = editUserQuery!!.length() <= 0
val noQuery = actionBarCustomView.editUserQuery!!.length() <= 0
if (noQuery) {
usersSearchEmptyText!!.setText(R.string.type_name_to_search)
} else {

View File

@ -0,0 +1,8 @@
package org.mariotaku.twidere.fragment
import android.support.v4.app.Fragment
/**
* Created by mariotaku on 16/3/28.
*/
class MessagesEntriesFragment : Fragment()

View File

@ -80,7 +80,7 @@
android:visibility="gone"/>
<org.mariotaku.twidere.view.IconActionView
android:id="@+id/send"
android:id="@+id/sendMessage"
android:layout_width="@dimen/element_size_normal"
android:layout_height="@dimen/element_size_normal"
android:layout_alignParentEnd="true"
@ -99,9 +99,9 @@
android:layout_alignWithParentIfMissing="true"
android:layout_gravity="end"
android:layout_toEndOf="@+id/addImage"
android:layout_toLeftOf="@+id/send"
android:layout_toLeftOf="@+id/sendMessage"
android:layout_toRightOf="@+id/addImage"
android:layout_toStartOf="@+id/send"
android:layout_toStartOf="@+id/sendMessage"
app:bubbleColor="?messageBubbleColor"
app:caretHeight="8dp"
app:caretPosition="topEnd"

View File

@ -26,12 +26,12 @@
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/list_container"
android:id="@+id/listContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mobeta.android.dslv.DragSortListView
android:id="@android:id/list"
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:collapsed_height="2dp"
@ -50,7 +50,7 @@
tools:visibility="gone"/>
<LinearLayout
android:id="@android:id/empty"
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
@ -58,13 +58,13 @@
tools:visibility="visible">
<org.mariotaku.twidere.view.IconActionView
android:id="@+id/empty_icon"
android:id="@+id/emptyIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:color="?android:textColorSecondary"/>
<TextView
android:id="@+id/empty_text"
android:id="@+id/emptyText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -75,12 +75,12 @@
</FrameLayout>
<FrameLayout
android:id="@+id/progress_container"
android:id="@+id/progressContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@android:id/progress"
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>