improved enter key behaviour in messages conversion and quick search bar

This commit is contained in:
Mariotaku Lee 2015-04-22 12:23:31 +08:00
parent 68ba8d458c
commit 0dce3fcdd5
11 changed files with 516 additions and 302 deletions

View File

@ -61,13 +61,13 @@ import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.support.v7.widget.RecyclerView.State;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.Editable;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -75,15 +75,12 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.EditorInfo;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import com.nostra13.universalimageloader.utils.IoUtils;
@ -109,6 +106,8 @@ import org.mariotaku.twidere.service.BackgroundOperationService;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.EditTextEnterHandler;
import org.mariotaku.twidere.util.EditTextEnterHandler.EnterListener;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MenuUtils;
@ -600,29 +599,12 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
mMenuBar.setOnMenuItemClickListener(this);
final boolean sendByEnter = mPreferences.getBoolean(KEY_QUICK_SEND);
mEditText.setOnKeyListener(new OnKeyListener() {
EditTextEnterHandler.attach(mEditText, new EnterListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER && sendByEnter && event.getAction() == KeyEvent.ACTION_DOWN) {
updateStatus();
return true;
}
return false;
public void onHitEnter() {
updateStatus();
}
});
mEditText.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
if (sendByEnter) {
if (event != null && actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) {
updateStatus();
return true;
}
}
return false;
}
});
}, sendByEnter);
mEditText.addTextChangedListener(new TextWatcher() {
@Override
@ -638,11 +620,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
@Override
public void afterTextChanged(final Editable s) {
final int length = s.length();
if (sendByEnter && length > 0 && s.charAt(length - 1) == '\n') {
s.delete(length - 1, length);
updateStatus();
}
}
});
mEditText.setCustomSelectionActionModeCallback(this);
@ -743,8 +720,8 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
startLocationUpdateIfEnabled();
setMenu();
updateTextCount();
final int text_size = mPreferences.getInt(KEY_TEXT_SIZE, Utils.getDefaultTextSize(this));
mEditText.setTextSize(text_size * 1.25f);
final int textSize = mPreferences.getInt(KEY_TEXT_SIZE, Utils.getDefaultTextSize(this));
mEditText.setTextSize(textSize * 1.25f);
}
@Override
@ -874,16 +851,16 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
}
mEditText.setText(Utils.getShareStatus(this, extraSubject, extraText));
final int selection_end = mEditText.length();
mEditText.setSelection(selection_end);
final int selectionEnd = mEditText.length();
mEditText.setSelection(selectionEnd);
return true;
}
private boolean handleEditDraftIntent(final DraftItem draft) {
if (draft == null) return false;
mEditText.setText(draft.text);
final int selection_end = mEditText.length();
mEditText.setSelection(selection_end);
final int selectionEnd = mEditText.length();
mEditText.setSelection(selectionEnd);
mAccountsAdapter.setSelectedAccountIds(draft.account_ids);
if (draft.media != null) {
addMedia(Arrays.asList(draft.media));

View File

@ -785,10 +785,10 @@ public class HomeActivity extends BaseActionBarActivity implements OnClickListen
return true;
}
private void setTabPosition(final int initial_tab) {
private void setTabPosition(final int initialTab) {
final boolean rememberPosition = mPreferences.getBoolean(KEY_REMEMBER_POSITION, true);
if (initial_tab >= 0) {
mViewPager.setCurrentItem(MathUtils.clamp(initial_tab, mPagerAdapter.getCount(), 0));
if (initialTab >= 0) {
mViewPager.setCurrentItem(MathUtils.clamp(initialTab, mPagerAdapter.getCount(), 0));
} else if (rememberPosition) {
final int position = mPreferences.getInt(KEY_SAVED_TAB_POSITION, 0);
mViewPager.setCurrentItem(MathUtils.clamp(position, mPagerAdapter.getCount(), 0));

View File

@ -35,7 +35,6 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@ -51,7 +50,6 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.querybuilder.Columns.Column;
@ -68,6 +66,8 @@ import org.mariotaku.twidere.model.ParcelableUser.CachedIndices;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers;
import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches;
import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory;
import org.mariotaku.twidere.util.EditTextEnterHandler;
import org.mariotaku.twidere.util.EditTextEnterHandler.EnterListener;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.SwipeDismissListViewTouchListener;
@ -87,8 +87,8 @@ import static org.mariotaku.twidere.util.UserColorNameUtils.getUserNickname;
* Created by mariotaku on 15/1/6.
*/
public class QuickSearchBarActivity extends ThemedFragmentActivity implements OnClickListener,
OnEditorActionListener, LoaderCallbacks<List<SuggestionItem>>, TextWatcher,
OnItemSelectedListener, OnItemClickListener, DismissCallbacks, OnFitSystemWindowsListener {
LoaderCallbacks<List<SuggestionItem>>, OnItemSelectedListener, OnItemClickListener,
DismissCallbacks, OnFitSystemWindowsListener {
private Spinner mAccountSpinner;
private EditText mSearchQuery;
@ -98,11 +98,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
private ExtendedRelativeLayout mMainContent;
private Rect mSystemWindowsInsets = new Rect();
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public boolean canDismiss(int position) {
return mUsersSearchAdapter.canDismiss(position);
@ -124,28 +119,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
getSupportLoaderManager().restartLoader(0, null, this);
}
@Override
public void onFitSystemWindows(Rect insets) {
mSystemWindowsInsets.set(insets);
updateWindowAttributes();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final SuggestionItem item = mUsersSearchAdapter.getItem(position);
item.onItemClick(this, position);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
getSupportLoaderManager().restartLoader(0, null, this);
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public int getThemeColor() {
return ThemeUtils.getUserAccentColor(this);
@ -156,43 +129,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
return ThemeUtils.getQuickSearchBarThemeResource(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quick_search_bar);
final List<ParcelableAccount> accounts = ParcelableAccount.getAccountsList(this, false);
final AccountsSpinnerAdapter accountsSpinnerAdapter = new AccountsSpinnerAdapter(this, R.layout.spinner_item_account_icon);
accountsSpinnerAdapter.setDropDownViewResource(R.layout.list_item_user);
accountsSpinnerAdapter.addAll(accounts);
mAccountSpinner.setAdapter(accountsSpinnerAdapter);
mAccountSpinner.setOnItemSelectedListener(this);
if (savedInstanceState == null) {
final Intent intent = getIntent();
final int index = accountsSpinnerAdapter.findItemPosition(intent.getLongExtra(EXTRA_ACCOUNT_ID, -1));
if (index != -1) {
mAccountSpinner.setSelection(index);
}
}
mMainContent.setOnFitSystemWindowsListener(this);
mUsersSearchAdapter = new SuggestionsAdapter(this);
mSuggestionsList.setAdapter(mUsersSearchAdapter);
mSuggestionsList.setOnItemClickListener(this);
final SwipeDismissListViewTouchListener listener = new SwipeDismissListViewTouchListener(mSuggestionsList, this);
mSuggestionsList.setOnTouchListener(listener);
mSuggestionsList.setOnScrollListener(listener.makeScrollListener());
mSearchSubmit.setOnClickListener(this);
mSearchQuery.setOnEditorActionListener(this);
mSearchQuery.addTextChangedListener(this);
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
protected void onResume() {
super.onResume();
updateWindowAttributes();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
@ -229,15 +165,15 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event == null) return false;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER: {
doSearch();
return true;
}
}
return false;
public void onFitSystemWindows(Rect insets) {
mSystemWindowsInsets.set(insets);
updateWindowAttributes();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final SuggestionItem item = mUsersSearchAdapter.getItem(position);
item.onItemClick(this, position);
}
@Override
@ -250,6 +186,68 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quick_search_bar);
final List<ParcelableAccount> accounts = ParcelableAccount.getAccountsList(this, false);
final AccountsSpinnerAdapter accountsSpinnerAdapter = new AccountsSpinnerAdapter(this, R.layout.spinner_item_account_icon);
accountsSpinnerAdapter.setDropDownViewResource(R.layout.list_item_user);
accountsSpinnerAdapter.addAll(accounts);
mAccountSpinner.setAdapter(accountsSpinnerAdapter);
mAccountSpinner.setOnItemSelectedListener(this);
if (savedInstanceState == null) {
final Intent intent = getIntent();
final int index = accountsSpinnerAdapter.findItemPosition(intent.getLongExtra(EXTRA_ACCOUNT_ID, -1));
if (index != -1) {
mAccountSpinner.setSelection(index);
}
}
mMainContent.setOnFitSystemWindowsListener(this);
mUsersSearchAdapter = new SuggestionsAdapter(this);
mSuggestionsList.setAdapter(mUsersSearchAdapter);
mSuggestionsList.setOnItemClickListener(this);
final SwipeDismissListViewTouchListener listener = new SwipeDismissListViewTouchListener(mSuggestionsList, this);
mSuggestionsList.setOnTouchListener(listener);
mSuggestionsList.setOnScrollListener(listener.makeScrollListener());
mSearchSubmit.setOnClickListener(this);
EditTextEnterHandler.attach(mSearchQuery, new EnterListener() {
@Override
public void onHitEnter() {
doSearch();
}
}, true);
mSearchQuery.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (Utils.removeLineBreaks(s)) {
doSearch();
} else {
getSupportLoaderManager().restartLoader(0, null, QuickSearchBarActivity.this);
}
}
});
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
protected void onResume() {
super.onResume();
updateWindowAttributes();
}
private void doSearch() {
if (isFinishing()) return;
final String query = ParseUtils.parseString(mSearchQuery.getText());
@ -263,6 +261,10 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
return mAccountSpinner.getSelectedItemId();
}
private static int getHistorySize(CharSequence query) {
return TextUtils.isEmpty(query) ? 3 : 2;
}
private void updateWindowAttributes() {
final Window window = getWindow();
final WindowManager.LayoutParams attributes = window.getAttributes();
@ -285,48 +287,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
static class SearchHistoryItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 0;
private final long mCursorId;
private final String mQuery;
public long getCursorId() {
return mCursorId;
}
public SearchHistoryItem(long cursorId, String query) {
mCursorId = cursorId;
mQuery = query;
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_search;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openSearch(activity, activity.getAccountId(), mQuery);
activity.finish();
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
text1.setText(mQuery);
icon.setImageResource(R.drawable.ic_action_history);
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
}
}
static abstract class BaseClickableItem implements SuggestionItem {
@Override
public final boolean isEnabled() {
@ -371,91 +331,47 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
}
static class UserSuggestionItem extends BaseClickableItem {
static class SearchHistoryItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 2;
private final ParcelableUser mUser;
static final int ITEM_VIEW_TYPE = 0;
private final long mCursorId;
private final String mQuery;
public UserSuggestionItem(Cursor c, CachedIndices i, long accountId) {
mUser = new ParcelableUser(c, i, accountId);
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
public ParcelableUser getUser() {
return mUser;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openUserProfile(activity, mUser, null);
activity.finish();
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_user;
public SearchHistoryItem(long cursorId, String query) {
mCursorId = cursorId;
mQuery = query;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final ParcelableUser user = mUser;
final Context context = adapter.getContext();
final MediaLoaderWrapper loader = adapter.getImageLoader();
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText(getUserNickname(context, user.id, user.name));
text2.setVisibility(View.VISIBLE);
text2.setText("@" + user.screen_name);
icon.clearColorFilter();
loader.displayProfileImage(icon, user.profile_image_url);
}
}
static class UserScreenNameItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 3;
private final String mScreenName;
private final long mAccountId;
public UserScreenNameItem(String screenName, long accountId) {
mScreenName = screenName;
mAccountId = accountId;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openUserProfile(activity, mAccountId, -1, mScreenName, null);
activity.finish();
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_user;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final MediaLoaderWrapper loader = adapter.getImageLoader();
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText('@' + mScreenName);
text2.setVisibility(View.GONE);
text1.setText(mQuery);
icon.setImageResource(R.drawable.ic_action_history);
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
loader.cancelDisplayTask(icon);
icon.setImageResource(R.drawable.ic_action_user);
}
public long getCursorId() {
return mCursorId;
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_search;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openSearch(activity, activity.getAccountId(), mQuery);
activity.finish();
}
}
public static class SuggestionsAdapter extends BaseAdapter {
@ -541,10 +457,6 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
}
private static int getHistorySize(CharSequence query) {
return TextUtils.isEmpty(query) ? 3 : 2;
}
public static class SuggestionsLoader extends AsyncTaskLoader<List<SuggestionItem>> {
private final long mAccountId;
@ -625,4 +537,92 @@ public class QuickSearchBarActivity extends ThemedFragmentActivity implements On
}
}
static class UserScreenNameItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 3;
private final String mScreenName;
private final long mAccountId;
public UserScreenNameItem(String screenName, long accountId) {
mScreenName = screenName;
mAccountId = accountId;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openUserProfile(activity, mAccountId, -1, mScreenName, null);
activity.finish();
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_user;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final MediaLoaderWrapper loader = adapter.getImageLoader();
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText('@' + mScreenName);
text2.setVisibility(View.GONE);
icon.setColorFilter(text1.getCurrentTextColor(), Mode.SRC_ATOP);
loader.cancelDisplayTask(icon);
icon.setImageResource(R.drawable.ic_action_user);
}
}
static class UserSuggestionItem extends BaseClickableItem {
static final int ITEM_VIEW_TYPE = 2;
private final ParcelableUser mUser;
public UserSuggestionItem(Cursor c, CachedIndices i, long accountId) {
mUser = new ParcelableUser(c, i, accountId);
}
public ParcelableUser getUser() {
return mUser;
}
@Override
public int getItemViewType() {
return ITEM_VIEW_TYPE;
}
@Override
public void onItemClick(QuickSearchBarActivity activity, int position) {
Utils.openUserProfile(activity, mUser, null);
activity.finish();
}
@Override
public final int getItemLayoutResource() {
return R.layout.list_item_suggestion_user;
}
@Override
public void bindView(SuggestionsAdapter adapter, View view, int position) {
final ParcelableUser user = mUser;
final Context context = adapter.getContext();
final MediaLoaderWrapper loader = adapter.getImageLoader();
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
final TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText(getUserNickname(context, user.id, user.name));
text2.setVisibility(View.VISIBLE);
text2.setText("@" + user.screen_name);
icon.clearColorFilter();
loader.displayProfileImage(icon, user.profile_image_url);
}
}
}

View File

@ -45,7 +45,6 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -62,7 +61,6 @@ import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
@ -91,6 +89,8 @@ import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Conversati
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ClipboardUtils;
import org.mariotaku.twidere.util.EditTextEnterHandler;
import org.mariotaku.twidere.util.EditTextEnterHandler.EnterListener;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.ReadStateManager;
@ -112,8 +112,8 @@ import static org.mariotaku.twidere.util.Utils.buildDirectMessageConversationUri
import static org.mariotaku.twidere.util.Utils.showOkMessage;
public class MessagesConversationFragment extends BaseSupportFragment implements
LoaderCallbacks<Cursor>, TextWatcher, OnClickListener, OnItemSelectedListener,
OnEditorActionListener, MenuButtonClickListener, PopupMenu.OnMenuItemClickListener {
LoaderCallbacks<Cursor>, OnClickListener, OnItemSelectedListener, MenuButtonClickListener,
PopupMenu.OnMenuItemClickListener {
private static final int LOADER_ID_SEARCH_USERS = 1;
@ -160,6 +160,7 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
@Override
public Loader<List<ParcelableUser>> onCreateLoader(int id, Bundle args) {
mUsersSearchList.setVisibility(View.GONE);
mUsersSearchEmpty.setVisibility(View.GONE);
mUsersSearchProgress.setVisibility(View.VISIBLE);
final long accountId = args.getLong(EXTRA_ACCOUNT_ID);
final String query = args.getString(EXTRA_QUERY);
@ -172,6 +173,7 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
public void onLoadFinished(Loader<List<ParcelableUser>> loader, List<ParcelableUser> data) {
mUsersSearchList.setVisibility(View.VISIBLE);
mUsersSearchProgress.setVisibility(View.GONE);
mUsersSearchEmpty.setVisibility(data == null || data.isEmpty() ? View.GONE : View.VISIBLE);
mUsersSearchAdapter.setData(data, true);
updateEmptyText();
}
@ -188,15 +190,6 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
updateRefreshState();
}
@Override
public void afterTextChanged(final Editable s) {
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
@ -230,24 +223,6 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
accountsSpinnerAdapter.addAll(accounts);
mAccountSpinner.setAdapter(accountsSpinnerAdapter);
mAccountSpinner.setOnItemSelectedListener(this);
mUserQuery.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
final ParcelableAccount account = (ParcelableAccount) mAccountSpinner.getSelectedItem();
mEditText.setAccountId(account.account_id);
searchUsers(account.account_id, ParseUtils.parseString(s), true);
}
@Override
public void afterTextChanged(Editable s) {
}
});
mQueryButton.setOnClickListener(this);
mAdapter = new MessageConversationAdapter(activity);
final LinearLayoutManager layoutManager = new FixedLinearLayoutManager(viewContext);
@ -268,11 +243,8 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
}
});
if (mPreferences.getBoolean(KEY_QUICK_SEND, false)) {
mEditText.setOnEditorActionListener(this);
}
mEditText.addTextChangedListener(this);
setupEditQuery();
setupEditText();
mSendButton.setOnClickListener(this);
mAddImageButton.setOnClickListener(this);
@ -319,6 +291,80 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
}
private void setupEditQuery() {
final EditTextEnterHandler queryEnterHandler = EditTextEnterHandler.attach(mUserQuery, new EnterListener() {
@Override
public void onHitEnter() {
final ParcelableAccount account = (ParcelableAccount) mAccountSpinner.getSelectedItem();
if (account == null) return;
mEditText.setAccountId(account.account_id);
searchUsers(account.account_id, ParseUtils.parseString(mUserQuery.getText()), false);
}
}, true);
queryEnterHandler.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
final ParcelableAccount account = (ParcelableAccount) mAccountSpinner.getSelectedItem();
if (account == null) return;
mEditText.setAccountId(account.account_id);
searchUsers(account.account_id, ParseUtils.parseString(s), true);
}
});
mUserQuery.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
Utils.removeLineBreaks(s);
}
});
}
private void setupEditText() {
EditTextEnterHandler.attach(mEditText, new EnterListener() {
@Override
public void onHitEnter() {
sendDirectMessage();
}
}, mPreferences.getBoolean(KEY_QUICK_SEND, false));
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(final Editable s) {
Utils.removeLineBreaks(s);
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
updateTextCount();
if (mSendButton == null || s == null) return;
mSendButton.setEnabled(mValidator.isValidTweet(s.toString()));
}
});
}
private String getDraftsTextKey(long accountId, long userId) {
return String.format(Locale.ROOT, "text_%d_to_%d", accountId, userId);
}
@ -444,17 +490,6 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
}
@Override
public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_ENTER: {
sendDirectMessage();
return true;
}
}
return false;
}
@Override
public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, final long id) {
final ParcelableAccount account = (ParcelableAccount) mAccountSpinner.getSelectedItem();
@ -585,12 +620,6 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
super.onStop();
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
updateTextCount();
if (mSendButton == null || s == null) return;
mSendButton.setEnabled(mValidator.isValidTweet(s.toString()));
}
private void updateEmptyText() {
final boolean noQuery = mUserQuery.length() <= 0;
@ -703,7 +732,11 @@ public class MessagesConversationFragment extends BaseSupportFragment implements
final ParcelableUser recipient = mRecipient;
if (mAccount == null || mRecipient == null) return;
final String message = mEditText.getText().toString();
if (mValidator.isValidTweet(message)) {
if (TextUtils.isEmpty(message)) {
mEditText.setError(getString(R.string.error_message_no_content));
} else if (mValidator.getTweetLength(message) > mValidator.getMaxTweetLength()) {
mEditText.setError(getString(R.string.error_message_message_too_long));
} else {
mTwitterWrapper.sendDirectMessageAsync(account.account_id, recipient.id, message, mImageUri);
mEditText.setText(null);
mImageUri = null;

View File

@ -46,6 +46,7 @@ import android.nfc.NfcEvent;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
@ -64,6 +65,7 @@ import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -109,6 +111,8 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Filters;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ColorUtils;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback;
import org.mariotaku.twidere.util.LinkCreator;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
@ -145,7 +149,7 @@ import twitter4j.TwitterException;
public class UserFragment extends BaseSupportFragment implements OnClickListener,
OnLinkClickListener, OnSizeChangedListener, OnSharedPreferenceChangeListener,
OnTouchListener, DrawerCallback, SupportFragmentCallback, SystemWindowsInsetsCallback,
RefreshScrollTopInterface, OnPageChangeListener {
RefreshScrollTopInterface, OnPageChangeListener, KeyboardShortcutCallback {
private static final ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();
@ -189,9 +193,11 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
private TextView mPagesErrorText;
private View mProfileNameBackground;
private View mProfileDetailsContainer;
private SupportTabsAdapter mPagerAdapter;
private Relationship mRelationship;
private SupportTabsAdapter mPagerAdapter;
private KeyboardShortcutsHandler mKeyboardShortcutsHandler;
private ParcelableUser mUser = null;
private Locale mLocale;
private boolean mGetUserInfoLoaderInitialized, mGetFriendShipLoaderInitialized;
@ -671,9 +677,13 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
getSharedPreferences(USER_NICKNAME_PREFERENCES_NAME, Context.MODE_PRIVATE)
.registerOnSharedPreferenceChangeListener(this);
mLocale = getResources().getConfiguration().locale;
mCardBackgroundColor = ThemeUtils.getCardBackgroundColor(activity, ThemeUtils.getThemeBackgroundOption(activity), ThemeUtils.getUserThemeBackgroundAlpha(activity));
mCardBackgroundColor = ThemeUtils.getCardBackgroundColor(activity,
ThemeUtils.getThemeBackgroundOption(activity),
ThemeUtils.getUserThemeBackgroundAlpha(activity));
mActionBarShadowColor = 0xA0000000;
mProfileImageLoader = getApplication().getMediaLoaderWrapper();
final TwidereApplication app = TwidereApplication.getInstance(activity);
mProfileImageLoader = app.getMediaLoaderWrapper();
mKeyboardShortcutsHandler = app.getKeyboardShortcutsHandler();
final Bundle args = getArguments();
long accountId = -1, userId = -1;
String screenName = null;
@ -1059,6 +1069,56 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener
mProfileDetailsContainer = view.findViewById(R.id.profile_details_container);
}
@Override
public boolean handleKeyboardShortcutSingle(int keyCode, @NonNull KeyEvent event) {
if (handleFragmentKeyboardShortcutSingle(keyCode, event)) return true;
final String action = mKeyboardShortcutsHandler.getKeyAction("navigation", keyCode, event);
if (action != null) {
switch (action) {
case "navigation.previous_tab": {
final int previous = mViewPager.getCurrentItem() - 1;
if (previous >= 0 && previous < mPagerAdapter.getCount()) {
mViewPager.setCurrentItem(previous, true);
}
return true;
}
case "navigation.next_tab": {
final int next = mViewPager.getCurrentItem() + 1;
if (next >= 0 && next < mPagerAdapter.getCount()) {
mViewPager.setCurrentItem(next, true);
}
return true;
}
}
}
return mKeyboardShortcutsHandler.handleKey(getActivity(), null, keyCode, event);
}
@Override
public boolean handleKeyboardShortcutRepeat(int keyCode, int repeatCount, @NonNull KeyEvent event) {
return handleFragmentKeyboardShortcutRepeat(keyCode, repeatCount, event);
}
private boolean handleFragmentKeyboardShortcutRepeat(int keyCode, int repeatCount, @NonNull KeyEvent event) {
final Fragment fragment = getKeyboardShortcutRecipient();
if (fragment instanceof KeyboardShortcutCallback) {
return ((KeyboardShortcutCallback) fragment).handleKeyboardShortcutRepeat(keyCode, repeatCount, event);
}
return false;
}
private boolean handleFragmentKeyboardShortcutSingle(int keyCode, @NonNull KeyEvent event) {
final Fragment fragment = getKeyboardShortcutRecipient();
if (fragment instanceof KeyboardShortcutCallback) {
return ((KeyboardShortcutCallback) fragment).handleKeyboardShortcutSingle(keyCode, event);
}
return false;
}
private Fragment getKeyboardShortcutRecipient() {
return getCurrentVisibleFragment();
}
@Override
protected void fitSystemWindows(Rect insets) {
super.fitSystemWindows(insets);

View File

@ -0,0 +1,131 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 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.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import java.util.ArrayList;
/**
* Created by mariotaku on 15/4/22.
*/
public class EditTextEnterHandler implements View.OnKeyListener, OnEditorActionListener, TextWatcher {
@Nullable
private EnterListener listener;
private boolean enabled;
private ArrayList<TextWatcher> textWatchers;
public EditTextEnterHandler(@Nullable EnterListener listener, boolean enabled) {
this.listener = listener;
this.enabled = enabled;
}
public void addTextChangedListener(TextWatcher watcher) {
if (textWatchers == null) {
textWatchers = new ArrayList<>();
}
textWatchers.add(watcher);
}
public static EditTextEnterHandler attach(@NonNull EditText editText, @Nullable EnterListener listener, boolean enabled) {
final EditTextEnterHandler enterHandler = new EditTextEnterHandler(listener, enabled);
editText.setOnKeyListener(enterHandler);
editText.setOnEditorActionListener(enterHandler);
editText.addTextChangedListener(enterHandler);
return enterHandler;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (textWatchers == null) return;
for (TextWatcher textWatcher : textWatchers) {
textWatcher.beforeTextChanged(s, start, count, after);
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (textWatchers == null) return;
for (TextWatcher textWatcher : textWatchers) {
textWatcher.onTextChanged(s, start, before, count);
}
}
@Override
public void afterTextChanged(final Editable s) {
final int length = s.length();
if (enabled && length > 0 && s.charAt(length - 1) == '\n') {
s.delete(length - 1, length);
if (listener != null) {
listener.onHitEnter();
}
} else if (textWatchers != null) {
for (TextWatcher textWatcher : textWatchers) {
textWatcher.afterTextChanged(s);
}
}
}
@Override
public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
if (!enabled) return false;
if (event != null && actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) {
if (listener != null) {
listener.onHitEnter();
}
return true;
}
return false;
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER && enabled && event.getAction() == KeyEvent.ACTION_DOWN) {
if (listener != null) {
listener.onHitEnter();
}
return true;
}
return false;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setListener(@Nullable EnterListener listener) {
this.listener = listener;
}
public static interface EnterListener {
void onHitEnter();
}
}

View File

@ -81,6 +81,7 @@ import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.ShareActionProvider;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateFormat;
@ -1501,6 +1502,17 @@ public final class Utils implements Constants, TwitterConstants {
return textView;
}
public static boolean removeLineBreaks(Editable s) {
boolean deleted = false;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '\n') {
s.delete(i, i + 1);
deleted |= true;
}
}
return deleted;
}
public static boolean setLastSeen(Context context, UserMentionEntity[] entities, long time) {
if (entities == null) return false;
boolean result = false;

View File

@ -47,7 +47,10 @@
android:layout_marginLeft="@dimen/element_spacing_normal"
android:layout_marginRight="@dimen/element_spacing_normal"
android:gravity="bottom"
android:inputType="textPersonName"/>
android:inputType="textPersonName|textMultiLine">
<requestFocus/>
</EditText>
<org.mariotaku.twidere.view.ActionIconButton
android:id="@+id/query_button"
@ -55,6 +58,7 @@
android:layout_width="@dimen/element_size_normal"
android:layout_height="@dimen/element_size_normal"
android:layout_gravity="right"
android:color="?android:textColorPrimary"
android:padding="@dimen/element_spacing_normal"
android:scaleType="centerInside"
android:src="@drawable/ic_action_search"/>

View File

@ -63,10 +63,7 @@
android:background="@android:color/transparent"
android:focusable="true"
android:hint="@string/search_hint"
android:imeActionLabel="@android:string/search_go"
android:imeOptions="actionSearch"
android:inputType="text"
android:singleLine="true">
android:inputType="text|textMultiLine">
<requestFocus/>
</EditText>

View File

@ -47,8 +47,7 @@
android:completionThreshold="1"
android:gravity="left|bottom"
android:hint="@string/type_to_compose"
android:imeOptions="actionDone"
android:inputType="textMultiLine"
android:inputType="textShortMessage|textMultiLine"
android:maxHeight="140dp"
android:minHeight="?android:actionBarSize"
android:singleLine="false"/>

View File

@ -244,6 +244,7 @@
<string name="status_shortener">Tweet shortener</string>
<string name="status_shortener_default">None (Abort sending)</string>
<string name="error_message_status_too_long">Tweet too long.</string>
<string name="error_message_message_too_long">Message too long.</string>
<string name="error_message_no_content">No content</string>
<string name="error_message_tweet_shorten_failed">Tweet shorten failed.</string>
<string name="error_message_tweet_shortener_not_found">Tweet shortener not found, maybe it was uninstalled.</string>