/* * Copyright (C) 2013 Chris Lacy Licensed under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.tweetlanes.android.core.view; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.os.Bundle; import android.support.v4.app.Fragment; import android.text.Editable; import android.text.TextWatcher; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.tweetlanes.android.core.App; import com.tweetlanes.android.core.AppSettings; import com.tweetlanes.android.core.Constant; import com.tweetlanes.android.core.R; import com.tweetlanes.android.core.model.AccountDescriptor; import com.tweetlanes.android.core.model.ComposeTweetDefault; import com.tweetlanes.android.core.util.LazyImageLoader; import com.tweetlanes.android.core.widget.EditClearText; import com.tweetlanes.android.core.widget.EditClearText.EditClearTextListener; import com.twitter.Validator; import org.socialnetlib.android.SocialNetConstant; import org.tweetalib.android.TwitterManager; import org.tweetalib.android.model.TwitterUser; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; abstract class ComposeBaseFragment extends Fragment { /* * */ public interface ComposeListener { public void onShowCompose(); public void onHideCompose(); public void onMediaAttach(); public void onMediaDetach(); public void onBackButtonPressed(); public void onStatusUpdateRequest(); public void onStatusUpdateSuccess(); public void onStatusHintUpdate(); public void saveDraft(String draftAsJsonString); public String getDraft(); } ImageButton mSendButton; EditClearText mEditText; private EditText mAutocompleteTarget; private TextView mCharacterCountTextView; private Long mShowStartTime; final Validator mStatusValidator = new Validator(); private ListView mAutocompleteListView; ComposeListener mListener; private boolean mHasFocus = false; private boolean mIgnoreFocusChange = false; boolean mUpdatingStatus = false; /* * */ App getApp() { if (getActivity() == null || getActivity().getApplication() == null) { return null; } return (App) getActivity().getApplication(); } /* * (non-Javadoc) * * @see * android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater, * android.view.ViewGroup, android.os.Bundle) */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View resultView = inflater.inflate(getLayoutResourceId(), null); mEditText = (EditClearText) resultView .findViewById(R.id.statusEditText); mEditText.addTextChangedListener(mTextChangedListener); mEditText.setOnFocusChangeListener(mOnFocusChangeListener); mEditText.setEditClearTextListener(mEditClearTextListener); mSendButton = (ImageButton) resultView .findViewById(R.id.sendTweetButton); mSendButton.setOnClickListener(mOnSendTweetClickListener); mCharacterCountTextView = (TextView) resultView .findViewById(R.id.characterCount); mCharacterCountTextView.setVisibility(View.VISIBLE); mAutocompleteListView = (ListView) resultView.findViewById(R.id.autocompleteListView); updateStatusHint(); return resultView; } /* * (non-Javadoc) * * @see android.support.v4.app.Fragment#onPause() */ @Override public void onPause() { super.onPause(); if (hasFocus()) { saveCurrentAsDraft(); } } /* * (non-Javadoc) * * @see android.support.v4.app.Fragment#onResume() */ @Override public void onResume() { super.onResume(); if (hasFocus()) { showCompose(); } } int getMaxPostLength() { if (getApp() == null) { return 140; } AccountDescriptor account = getApp().getCurrentAccount(); if (account == null) { return 140; //best to use the lower number in case } else { return account.getSocialNetType() == SocialNetConstant.Type.Appdotnet ? 256 : 140; } } /* * */ void showToast(String message) { if (getActivity() != null && getActivity().getApplicationContext() != null) { Toast.makeText(getActivity().getApplicationContext(), message, Constant.DEFAULT_TOAST_DISPLAY_TIME).show(); } } /* * */ void showSimpleAlert(int stringID) { AlertDialog alertDialog = new AlertDialog.Builder(getActivity()) .create(); alertDialog.setMessage(getString(stringID)); alertDialog.setButton(getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { } }); alertDialog.show(); } /* * */ public boolean hasFocus() { return mHasFocus; } /* * */ void setComposeTweetListener(ComposeListener listener) { mListener = listener; } /* * */ public void releaseFocus(boolean saveCurrentTweet) { clearCompose(saveCurrentTweet); setMediaPreviewVisibility(); hideCompose(); } /* * */ boolean hideCompose() { if (mHasFocus) { hideKeyboard(); if (mAutocompleteListView != null) { mAutocompleteListView.setVisibility(View.GONE); } if (mListener != null) { mListener.onHideCompose(); } onHideCompose(); mHasFocus = false; return true; } return false; } /* * */ void showKeyboard() { InputMethodManager inputMethodManager = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT); } void hideKeyboard() { InputMethodManager inputMethodManager = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); } /* * */ private final OnFocusChangeListener mOnFocusChangeListener = new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (hasFocus && !mIgnoreFocusChange) { showCompose(); setMediaPreviewVisibility(); } } }; /* * Used as a bit of a hack to prevent the Compose Tweet view enabling when * coming back from the ActionBar Search */ void setIgnoreFocusChange(boolean ignoreFocusChange) { mIgnoreFocusChange = ignoreFocusChange; } /* * */ private final TextWatcher mTextChangedListener = new TextWatcher() { public void afterTextChanged(Editable s) { String asString = s.toString(); configureCharacterCountForString(asString); if (asString == null || asString.equals("")) { if (mListener.getDraft() == null) { setComposeTweetDefault(null); updateStatusHint(); } } autoComplete(asString, mEditText); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } }; void autoComplete(String text, EditText editText) { if (mAutocompleteListView == null) { return; } if (text == null || text.length() == 0) { mAutocompleteListView.setVisibility(View.GONE); return; } int currentStart = editText.getSelectionStart(); int currentEnd = editText.getSelectionEnd(); int currentPosition = text.length() - 1; if (currentStart == currentEnd) { currentPosition = currentStart; } if (currentPosition < 0) currentPosition = 0; String lastWholeWord = ""; for (int i = currentPosition; i > 0; i--) { String nextChar = text.substring(i-1,i).toLowerCase(); if (nextChar.equals(" ") || nextChar.equals(".")) { break; } lastWholeWord = nextChar + lastWholeWord; } if (lastWholeWord.startsWith("@")) { List<TwitterUser> autoCompleteMentions = getAutoCompleteMentions(lastWholeWord); mAutocompleteListView.setVisibility(View.VISIBLE); mAutocompleteListView.setAdapter(new AutoCompleteMentionAdapter(this.getActivity(), autoCompleteMentions)); mAutocompleteTarget = editText; mAutocompleteListView.setOnItemClickListener(mOnAutoCompleteItemClickListener); } else if (lastWholeWord.startsWith("#")) { List<String> autoCompleteHashtags = getAutoCompleteHashtags(lastWholeWord); mAutocompleteListView.setVisibility(View.VISIBLE); mAutocompleteListView.setAdapter(new AutoCompleteHashtagAdapter(this.getActivity(), autoCompleteHashtags)); mAutocompleteTarget = editText; mAutocompleteListView.setOnItemClickListener(mOnAutoCompleteItemClickListener); } else { mAutocompleteListView.setVisibility(View.GONE); } } private class AutoCompleteMentionAdapter extends android.widget.BaseAdapter { final Context mContext; final List<TwitterUser> mData; public AutoCompleteMentionAdapter(Context context, List<TwitterUser> data) { mContext = context; mData = data; } @Override public int getCount() { return mData.size(); } @Override public TwitterUser getItem(int i) { return mData.get(i); } @Override public long getItemId(int i) { return getItem(i).getId(); } @Override public View getView(int position, View convertView, ViewGroup parent) { View row = convertView; UserHolder holder; if (row == null) { LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); row = inflater.inflate(R.layout.autocompletemention_row, parent, false); holder = new UserHolder(); holder.AvatarImage = (ImageView) row.findViewById(R.id.autoCompleteAvatar); holder.ScreenName = (TextView) row.findViewById(R.id.autoCompleteScreenName); holder.FullName = (TextView) row.findViewById(R.id.autoCompleteFullName); int dimensionValue = mContext.getResources().getDimensionPixelSize(R.dimen.font_size_medium); int imageSize = (int) mContext.getResources().getDimension(R.dimen.font_size_medium); holder.ScreenName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dimensionValue); holder.FullName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dimensionValue); holder.AvatarImage.setMaxWidth(imageSize); holder.AvatarImage.setMaxHeight(imageSize); row.setTag(holder); } else { holder = (UserHolder) row.getTag(); } TwitterUser user = mData.get(position); if (user == null) { return row; } holder.ScreenName.setText("@" + user.getScreenName(), TextView.BufferType.NORMAL); holder.FullName.setText(user.getName(), TextView.BufferType.NORMAL); setProfileImage(user, holder.AvatarImage); return row; } class UserHolder { public ImageView AvatarImage; public TextView ScreenName; public TextView FullName; } } private class AutoCompleteHashtagAdapter extends android.widget.BaseAdapter { final Context mContext; final List<String> mData; public AutoCompleteHashtagAdapter(Context context, List<String> data) { mContext = context; mData = data; } @Override public int getCount() { return mData.size(); } @Override public String getItem(int i) { return mData.get(i); } @Override public long getItemId(int i) { return getItem(i).hashCode(); } @Override public View getView(int position, View convertView, ViewGroup parent) { View row = convertView; HashtagHolder holder; if (row == null) { LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); row = inflater.inflate(R.layout.autocompletehashtag_row, parent, false); holder = new HashtagHolder(); holder.Hashtag = (TextView) row.findViewById(R.id.autoCompleteHashtag); int dimensionValue = mContext.getResources().getDimensionPixelSize(R.dimen.font_size_medium); holder.Hashtag.setTextSize(TypedValue.COMPLEX_UNIT_PX, dimensionValue); row.setTag(holder); } else { holder = (HashtagHolder) row.getTag(); } String hashtag = mData.get(position); if (hashtag == null) { return row; } holder.Hashtag.setText(hashtag, TextView.BufferType.NORMAL); return row; } class HashtagHolder { public TextView Hashtag; } } private void setProfileImage(TwitterUser user, ImageView avatar) { String profileImageUrl = user.getProfileImageUrl(TwitterManager.ProfileImageSize.NORMAL); if (profileImageUrl != null) { if (AppSettings.get().downloadFeedImages()) { LazyImageLoader profileImageLoader = getApp().getProfileImageLoader(); if (profileImageLoader != null) { profileImageLoader.displayImage(profileImageUrl, avatar); } } } } private final AdapterView.OnItemClickListener mOnAutoCompleteItemClickListener = new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { TextView textView = (TextView) view.findViewById(R.id.autoCompleteScreenName); if (textView == null) { textView = (TextView) view.findViewById(R.id.autoCompleteHashtag); } String autoCompleteText = String.valueOf(textView.getText()); String editText = String.valueOf(mAutocompleteTarget.getText()); int currentStart = mAutocompleteTarget.getSelectionStart(); int currentEnd = mAutocompleteTarget.getSelectionEnd(); int currentPosition = editText.length() - 1; if (currentStart == currentEnd) { currentPosition = currentStart; } if (currentPosition < 0) currentPosition = 0; int autoCompleteStart = 0; for (int i = currentPosition; i > 0; i--) { String nextChar = editText.substring(i-1,i).toLowerCase(); if (nextChar.equals(" ") || nextChar.equals(".")) { break; } autoCompleteStart = i-1; } String newText = editText.substring(0,autoCompleteStart) + autoCompleteText + " " + editText.substring(currentPosition); mAutocompleteTarget.setText(newText); mAutocompleteTarget.setSelection(autoCompleteStart + autoCompleteText.length()); mAutocompleteListView.setVisibility(View.GONE); } }; private ArrayList<TwitterUser> getAutoCompleteMentions(String text) { ArrayList<TwitterUser> list = new ArrayList<TwitterUser>(); List<TwitterUser> users = TwitterManager.get().getFetchUserInstance().getCachedUsers(); text = text.replace("@", ""); for (TwitterUser user : users) { if (user.getScreenName().toLowerCase().contains(text.toLowerCase()) || user.getName().toLowerCase().contains(text.toLowerCase())) { list.add(user); } } Collections.sort(list, new SortUserName()); return list; } private ArrayList<String> getAutoCompleteHashtags(String text) { ArrayList<String> list = new ArrayList<String>(); List<String> hashtags = TwitterManager.get().getFetchStatusesInstance().getCachedHashtags(); if (hashtags == null) { return list; } for (String tag : hashtags) { if (tag.toLowerCase().startsWith(text.toLowerCase())) { list.add(tag); } } Collections.sort(list, new SortAlpha()); return list; } private class SortUserName implements Comparator<Object> { public int compare(Object o1, Object o2) { TwitterUser s1 = (TwitterUser) o1; TwitterUser s2 = (TwitterUser) o2; return s1.getScreenName().toLowerCase().compareTo(s2.getScreenName().toLowerCase()); } } private class SortAlpha implements Comparator<Object> { public int compare(Object o1, Object o2) { String s1 = (String) o1; String s2 = (String) o2; return s1.toLowerCase().compareTo(s2.toLowerCase()); } } protected void lockScreenRotation() { if (getActivity() != null) { switch (getActivity().getResources().getConfiguration().orientation) { case Configuration.ORIENTATION_PORTRAIT: getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; case Configuration.ORIENTATION_LANDSCAPE: getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); break; } } } protected void resetScreenRotation() { if (getActivity() != null) { getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } } /* * */ private final OnClickListener mOnSendTweetClickListener = new OnClickListener() { @Override public void onClick(View v) { lockScreenRotation(); String status = mEditText.getText().toString(); if (status == null || status.equals("")) { ComposeTweetDefault composeTweetDefault = getComposeTweetDefault(); if (composeTweetDefault != null) { status = composeTweetDefault.getStatus(); } } onSendClick(status); } }; protected abstract void onSendClick(String status); protected abstract void setMediaPreviewVisibility(); /* */ private final EditClearTextListener mEditClearTextListener = new EditClearTextListener() { @Override public boolean canClearText() { if (mShowStartTime != null) { long diff = new Date().getTime() - mShowStartTime.longValue(); if (diff < 500) { return false; } } return true; } @Override public boolean onBackButtonPressed() { if (mListener != null) { mListener.onBackButtonPressed(); } hideCompose(); setMediaPreviewVisibility(); return true; } @Override public void onTouch(View v, MotionEvent event) { if (!mHasFocus) { showCompose(); setMediaPreviewVisibility(); } } }; /* * */ void clearCompose(boolean saveCurrentTweet) { if (saveCurrentTweet) { saveCurrentAsDraft(); updateComposeTweetDefault(); } else { setComposeTweetDefault(null); if (mListener != null) { mListener.onMediaDetach(); } if (getApp() != null) { getApp().clearTweetDraft(); } } // NOTE: Changing these text values causes a crash during the copy/paste // process. mEditText.setText(null); updateStatusHint(); } /* * */ void showCompose() { showCompose(null); } void showCompose(String defaultStatus) { if (!mHasFocus) { mHasFocus = true; mShowStartTime = new Date().getTime(); if (defaultStatus == null) { if (getComposeTweetDefault() == null && mListener != null) { String savedDraftAsJsonString = mListener.getDraft(); if (savedDraftAsJsonString != null) { setComposeTweetDefault(new ComposeTweetDefault( savedDraftAsJsonString)); } } if (getComposeTweetDefault() != null) { defaultStatus = getComposeTweetDefault().getStatus(); } } mEditText.setText(defaultStatus); if (defaultStatus != null && defaultStatus.length() > 1) { mEditText.setSelection(defaultStatus.length()); configureCharacterCountForString(defaultStatus); } if (mListener != null) { mListener.onShowCompose(); } onShowCompose(); } showKeyboard(); } /* * */ static String getStatusHintSnippet(String status, int maxLength) { if (status.length() == 0) { return null; } else if (status.length() < maxLength) { return status; } return status.substring(0, maxLength) + "…"; } /* * */ ComposeTweetDefault _mComposeDefault; ComposeTweetDefault getComposeTweetDefault() { return _mComposeDefault; } /* * */ void setComposeTweetDefault( ComposeTweetDefault composeTweetDefault) { _mComposeDefault = composeTweetDefault; } /* * */ public void setComposeDefault(ComposeTweetDefault other) { if (other != null) { setComposeTweetDefault(new ComposeTweetDefault(other)); } else { setComposeTweetDefault(null); } updateStatusHint(); } /* * */ void configureCharacterCountForString(String string) { if (string != null) { int length = mStatusValidator.getTweetLength(string); if (length > 0) { int remaining = getMaxPostLength() - length; if (_mComposeDefault != null && _mComposeDefault.getMediaFilePath() != null) { remaining -= 22; } mCharacterCountTextView.setText("" + remaining); } else { mCharacterCountTextView.setText("" + getMaxPostLength()); } } else { mCharacterCountTextView.setText(""); } } protected abstract void saveCurrentAsDraft(); protected abstract void updateStatusHint(); protected abstract void updateComposeTweetDefault(); protected abstract String getTweetDefaultDraft(); protected abstract void setTweetDefaultFromDraft(String tweetDraftAsJson); protected abstract int getLayoutResourceId(); protected abstract void onShowCompose(); protected abstract void onHideCompose(); }