Java tutorial
/* * 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.activity.support; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; 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.res.Resources; import android.graphics.Bitmap; import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Action; import android.support.v4.util.LongSparseArray; import android.support.v7.internal.view.SupportMenuInflater; import android.support.v7.widget.ActionMenuView; import android.support.v7.widget.ActionMenuView.OnMenuItemClickListener; import android.support.v7.widget.FixedLinearLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.Adapter; 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.Spannable; import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.text.style.ImageSpan; 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; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.Window; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.github.johnpersano.supertoasts.SuperToast; import com.github.johnpersano.supertoasts.SuperToast.Duration; import com.github.johnpersano.supertoasts.SuperToast.OnDismissListener; import com.nostra13.universalimageloader.utils.IoUtils; import com.twitter.Extractor; import org.mariotaku.dynamicgridview.DraggableArrayAdapter; import org.mariotaku.twidere.R; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.constant.SharedPreferenceConstants; import org.mariotaku.twidere.fragment.support.BaseSupportDialogFragment; import org.mariotaku.twidere.fragment.support.DraftsFragment; import org.mariotaku.twidere.fragment.support.ViewStatusDialogFragment; import org.mariotaku.twidere.model.DraftItem; import org.mariotaku.twidere.model.ParcelableAccount; import org.mariotaku.twidere.model.ParcelableLocation; import org.mariotaku.twidere.model.ParcelableMedia; import org.mariotaku.twidere.model.ParcelableMediaUpdate; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.model.ParcelableStatusUpdate; import org.mariotaku.twidere.model.ParcelableUser; import org.mariotaku.twidere.preference.ServicePickerPreference; import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; import org.mariotaku.twidere.service.BackgroundOperationService; import org.mariotaku.twidere.text.MarkForDeleteSpan; 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.KeyboardShortcutsHandler; import org.mariotaku.twidere.util.MathUtils; import org.mariotaku.twidere.util.MediaLoaderWrapper; import org.mariotaku.twidere.util.MenuUtils; import org.mariotaku.twidere.util.ParseUtils; import org.mariotaku.twidere.util.SharedPreferencesWrapper; import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.TwidereArrayUtils; import org.mariotaku.twidere.util.TwidereValidator; import org.mariotaku.twidere.util.UserColorNameManager; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.view.ActionIconView; import org.mariotaku.twidere.view.BadgeView; import org.mariotaku.twidere.view.ComposeEditText; import org.mariotaku.twidere.view.ShapedImageView; import org.mariotaku.twidere.view.StatusTextCountView; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.TreeSet; public class ComposeActivity extends ThemedFragmentActivity implements LocationListener, OnMenuItemClickListener, OnClickListener, OnLongClickListener, Callback { // Constants private static final String FAKE_IMAGE_LINK = "https://www.example.com/fake_image.jpg"; private static final String EXTRA_IS_POSSIBLY_SENSITIVE = "is_possibly_sensitive"; private static final String EXTRA_SHOULD_SAVE_ACCOUNTS = "should_save_accounts"; private static final String EXTRA_ORIGINAL_TEXT = "original_text"; private static final String EXTRA_SHARE_SCREENSHOT = "share_screenshot"; // Utility classes private final Extractor mExtractor = new Extractor(); private TwidereValidator mValidator; private AsyncTwitterWrapper mTwitterWrapper; private LocationManager mLocationManager; private SharedPreferencesWrapper mPreferences; private ContentResolver mResolver; private AsyncTask<Object, Object, ?> mTask; private SupportMenuInflater mMenuInflater; // Views private GridView mMediaPreviewGrid; private ActionMenuView mMenuBar; private ComposeEditText mEditText; private View mSendView; private StatusTextCountView mSendTextCountView; private RecyclerView mAccountSelector; private View mAccountSelectorContainer; private DraftItem mDraftItem; private ShapedImageView mProfileImageView; private BadgeView mCountView; private View mAccountSelectorButton; private MediaLoaderWrapper mImageLoader; private View mLocationContainer; private ActionIconView mLocationIcon; private TextView mLocationText; // Adapters private MediaPreviewAdapter mMediaPreviewAdapter; private AccountIconsAdapter mAccountsAdapter; // Data fields private ParcelableLocation mRecentLocation; private ParcelableStatus mInReplyToStatus; private ParcelableUser mMentionUser; private String mOriginalText; private long mInReplyToStatusId; private boolean mIsPossiblySensitive, mShouldSaveAccounts; private boolean mImageUploaderUsed, mStatusShortenerUsed; private boolean mNavigateBackPressed; private boolean mTextChanged; @Override public int getThemeColor() { return ThemeUtils.getUserAccentColor(this); } @Override public int getThemeResourceId() { return ThemeUtils.getComposeThemeResource(this); } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { switch (requestCode) { case REQUEST_TAKE_PHOTO: case REQUEST_PICK_IMAGE: case REQUEST_OPEN_DOCUMENT: { if (resultCode == Activity.RESULT_OK) { final Uri src = intent.getData(); mTask = AsyncTaskUtils.executeTask( new AddMediaTask(this, src, createTempImageUri(), ParcelableMedia.TYPE_IMAGE, true)); } break; } case REQUEST_EDIT_IMAGE: { if (resultCode == Activity.RESULT_OK) { final Uri uri = intent.getData(); if (uri == null) { break; } setMenu(); updateTextCount(); } break; } case REQUEST_EXTENSION_COMPOSE: { if (resultCode == Activity.RESULT_OK) { final String text = intent.getStringExtra(EXTRA_TEXT); final String append = intent.getStringExtra(EXTRA_APPEND_TEXT); final Uri imageUri = intent.getParcelableExtra(EXTRA_IMAGE_URI); if (text != null) { mEditText.setText(text); } else if (append != null) { mEditText.append(append); } if (imageUri != null) { } setMenu(); updateTextCount(); } break; } } } @Override public void onBackPressed() { if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) return; final String text = mEditText != null ? ParseUtils.parseString(mEditText.getText()) : null; final boolean textChanged = text != null && !text.isEmpty() && !text.equals(mOriginalText); final boolean isEditingDraft = INTENT_ACTION_EDIT_DRAFT.equals(getIntent().getAction()); if (textChanged || hasMedia() || isEditingDraft) { saveToDrafts(); Toast.makeText(this, R.string.status_saved_to_draft, Toast.LENGTH_SHORT).show(); finish(); } else { mTask = AsyncTaskUtils.executeTask(new DiscardTweetTask(this)); } } @Override public void onSaveInstanceState(final Bundle outState) { outState.putLongArray(EXTRA_ACCOUNT_IDS, mAccountsAdapter.getSelectedAccountIds()); outState.putParcelableArrayList(EXTRA_MEDIA, new ArrayList<Parcelable>(getMediaList())); outState.putBoolean(EXTRA_IS_POSSIBLY_SENSITIVE, mIsPossiblySensitive); outState.putParcelable(EXTRA_STATUS, mInReplyToStatus); outState.putLong(EXTRA_STATUS_ID, mInReplyToStatusId); outState.putParcelable(EXTRA_USER, mMentionUser); outState.putParcelable(EXTRA_DRAFT, mDraftItem); outState.putBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS, mShouldSaveAccounts); outState.putString(EXTRA_ORIGINAL_TEXT, mOriginalText); super.onSaveInstanceState(outState); } @Override protected void onStart() { super.onStart(); mImageUploaderUsed = !ServicePickerPreference.isNoneValue(mPreferences.getString(KEY_MEDIA_UPLOADER, null)); mStatusShortenerUsed = !ServicePickerPreference .isNoneValue(mPreferences.getString(KEY_STATUS_SHORTENER, null)); startLocationUpdateIfEnabled(); setMenu(); updateTextCount(); final int textSize = mPreferences.getInt(KEY_TEXT_SIZE, Utils.getDefaultTextSize(this)); mEditText.setTextSize(textSize * 1.25f); } @Override protected void onStop() { saveAccountSelection(); mLocationManager.removeUpdates(this); super.onStop(); } @Override public void onClick(final View view) { switch (view.getId()) { case R.id.send: { if (isQuotingProtectedStatus()) { new RetweetProtectedStatusWarnFragment().show(getSupportFragmentManager(), "retweet_protected_status_warning_message"); } else { updateStatus(); } break; } case R.id.account_selector_container: { setAccountSelectorVisible(false); break; } case R.id.account_selector_button: { final boolean isVisible = mAccountSelectorContainer.getVisibility() == View.VISIBLE; mAccountSelectorContainer.setVisibility(isVisible ? View.GONE : View.VISIBLE); break; } case R.id.location_container: { toggleLocation(); break; } } } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { final Window window = getWindow(); final Rect rect = new Rect(); window.getDecorView().getWindowVisibleDisplayFrame(rect); final int actionBarHeight = ThemeUtils.getActionBarHeight(this); final View contentView = window.findViewById(android.R.id.content); final int[] location = new int[2]; contentView.getLocationOnScreen(location); if (location[1] > actionBarHeight) { contentView.setPadding(contentView.getPaddingLeft(), 0, contentView.getPaddingRight(), contentView.getPaddingBottom()); return true; } final int statusBarHeight = rect.top; contentView.getWindowVisibleDisplayFrame(rect); final int paddingTop = statusBarHeight + actionBarHeight - rect.top; contentView.setPadding(contentView.getPaddingLeft(), paddingTop, contentView.getPaddingRight(), contentView.getPaddingBottom()); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; } @Override public void onDestroyActionMode(ActionMode mode) { final Window window = getWindow(); final View contentView = window.findViewById(android.R.id.content); contentView.setPadding(contentView.getPaddingLeft(), 0, contentView.getPaddingRight(), contentView.getPaddingBottom()); } @Override public void onLocationChanged(final Location location) { setRecentLocation(ParcelableLocation.fromLocation(location)); } @Override public void onStatusChanged(final String provider, final int status, final Bundle extras) { } @Override public void onProviderEnabled(final String provider) { } @Override public void onProviderDisabled(final String provider) { } @Override public boolean onLongClick(final View v) { switch (v.getId()) { case R.id.send: { Utils.showMenuItemToast(v, getString(R.string.send), true); return true; } } return false; } @Override public boolean onMenuItemClick(final MenuItem item) { switch (item.getItemId()) { case MENU_TAKE_PHOTO: case R.id.take_photo_sub_item: { takePhoto(); break; } case MENU_ADD_IMAGE: case R.id.add_image_sub_item: { pickImage(); break; } case MENU_ADD_LOCATION: { toggleLocation(); break; } case MENU_DRAFTS: { Utils.openDrafts(this); break; } case MENU_DELETE: { AsyncTaskUtils.executeTask(new DeleteImageTask(this)); break; } case MENU_TOGGLE_SENSITIVE: { if (!hasMedia()) return false; mIsPossiblySensitive = !mIsPossiblySensitive; setMenu(); updateTextCount(); break; } case MENU_VIEW: { if (mInReplyToStatus == null) return false; final DialogFragment fragment = new ViewStatusDialogFragment(); final Bundle args = new Bundle(); args.putParcelable(EXTRA_STATUS, mInReplyToStatus); fragment.setArguments(args); fragment.show(getSupportFragmentManager(), "view_status"); break; } case R.id.link_to_quoted_status: { final boolean newValue = !item.isChecked(); item.setChecked(newValue); mPreferences.edit().putBoolean(KEY_LINK_TO_QUOTED_TWEET, newValue).apply(); break; } default: { final Intent intent = item.getIntent(); if (intent != null) { try { final String action = intent.getAction(); if (INTENT_ACTION_EXTENSION_COMPOSE.equals(action)) { final long[] accountIds = mAccountsAdapter.getSelectedAccountIds(); intent.putExtra(EXTRA_TEXT, ParseUtils.parseString(mEditText.getText())); intent.putExtra(EXTRA_ACCOUNT_IDS, accountIds); if (accountIds.length > 0) { final long account_id = accountIds[0]; intent.putExtra(EXTRA_NAME, Utils.getAccountName(this, account_id)); intent.putExtra(EXTRA_SCREEN_NAME, Utils.getAccountScreenName(this, account_id)); } if (mInReplyToStatusId > 0) { intent.putExtra(EXTRA_IN_REPLY_TO_ID, mInReplyToStatusId); } if (mInReplyToStatus != null) { intent.putExtra(EXTRA_IN_REPLY_TO_NAME, mInReplyToStatus.user_name); intent.putExtra(EXTRA_IN_REPLY_TO_SCREEN_NAME, mInReplyToStatus.user_screen_name); } startActivityForResult(intent, REQUEST_EXTENSION_COMPOSE); } else if (INTENT_ACTION_EXTENSION_EDIT_IMAGE.equals(action)) { // final ComponentName cmp = intent.getComponent(); // if (cmp == null || !hasMedia()) return false; // final String name = new // File(mMediaUri.getPath()).getName(); // final Uri data = // Uri.withAppendedPath(CacheFiles.CONTENT_URI, // Uri.encode(name)); // intent.setData(data); // grantUriPermission(cmp.getPackageName(), data, // Intent.FLAG_GRANT_READ_URI_PERMISSION); // startActivityForResult(intent, // REQUEST_EDIT_IMAGE); } else { startActivity(intent); } } catch (final ActivityNotFoundException e) { Log.w(LOGTAG, e); return false; } } break; } } return true; } @Override public boolean onTouchEvent(final MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { final Rect rect = new Rect(); getWindow().getDecorView().getHitRect(rect); if (!rect.contains(Math.round(event.getX()), Math.round(event.getY()))) { onBackPressed(); return true; } break; } } return super.onTouchEvent(event); } @Override public void onContentChanged() { super.onContentChanged(); mEditText = (ComposeEditText) findViewById(R.id.edit_text); mMediaPreviewGrid = (GridView) findViewById(R.id.media_thumbnail_preview); mMenuBar = (ActionMenuView) findViewById(R.id.menu_bar); mSendView = findViewById(R.id.send); mSendTextCountView = (StatusTextCountView) mSendView.findViewById(R.id.status_text_count); mAccountSelector = (RecyclerView) findViewById(R.id.account_selector); mAccountSelectorContainer = findViewById(R.id.account_selector_container); mProfileImageView = (ShapedImageView) findViewById(R.id.account_profile_image); mCountView = (BadgeView) findViewById(R.id.accounts_count); mAccountSelectorButton = findViewById(R.id.account_selector_button); mLocationContainer = findViewById(R.id.location_container); mLocationIcon = (ActionIconView) findViewById(R.id.location_icon); mLocationText = (TextView) findViewById(R.id.location_text); } @NonNull @Override public MenuInflater getMenuInflater() { if (mMenuInflater == null) { mMenuInflater = new SupportMenuInflater(this); } return mMenuInflater; } public void removeAllMedia(final List<ParcelableMediaUpdate> list) { mMediaPreviewAdapter.removeAll(list); updateMediaPreview(); } public void saveToDrafts() { final String text = mEditText != null ? ParseUtils.parseString(mEditText.getText()) : null; final ParcelableStatusUpdate.Builder builder = new ParcelableStatusUpdate.Builder(); builder.accounts(ParcelableAccount.getAccounts(this, mAccountsAdapter.getSelectedAccountIds())); builder.text(text); builder.inReplyToStatusId(mInReplyToStatusId); builder.location(mRecentLocation); builder.isPossiblySensitive(mIsPossiblySensitive); if (hasMedia()) { builder.media(getMedia()); } final ContentValues values = ContentValuesCreator.createStatusDraft(builder.build()); final Uri draftUri = mResolver.insert(Drafts.CONTENT_URI, values); displayNewDraftNotification(text, draftUri); } public void setSelectedAccounts(ParcelableAccount... accounts) { if (accounts.length == 1) { mCountView.setText(null); final ParcelableAccount account = accounts[0]; mImageLoader.displayProfileImage(mProfileImageView, account.profile_image_url); mProfileImageView.setBorderColor(account.color); } else { mCountView.setText(String.valueOf(accounts.length)); mImageLoader.cancelDisplayTask(mProfileImageView); mProfileImageView.setImageDrawable(null); mProfileImageView.setBorderColors(Utils.getAccountColors(accounts)); } } @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); mPreferences = SharedPreferencesWrapper.getInstance(this, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE, SharedPreferenceConstants.class); final TwidereApplication app = TwidereApplication.getInstance(this); mTwitterWrapper = app.getTwitterWrapper(); mResolver = getContentResolver(); mValidator = new TwidereValidator(this); mImageLoader = app.getMediaLoaderWrapper(); setContentView(R.layout.activity_compose); setFinishOnTouchOutside(false); final long[] defaultAccountIds = Utils.getAccountIds(this); if (defaultAccountIds.length <= 0) { final Intent intent = new Intent(INTENT_ACTION_TWITTER_LOGIN); intent.setClass(this, SignInActivity.class); startActivity(intent); finish(); return; } mMenuBar.setOnMenuItemClickListener(this); setupEditText(); mAccountSelectorContainer.setOnClickListener(this); mAccountSelectorButton.setOnClickListener(this); mLocationContainer.setOnClickListener(this); final LinearLayoutManager linearLayoutManager = new FixedLinearLayoutManager(this); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); linearLayoutManager.setStackFromEnd(true); mAccountSelector.setLayoutManager(linearLayoutManager); mAccountSelector.addItemDecoration(new SpacingItemDecoration(this)); mAccountSelector.setItemAnimator(new RecyclerView.ItemAnimator() { @Override public void runPendingAnimations() { } @Override public boolean animateRemove(ViewHolder holder) { return false; } @Override public boolean animateAdd(ViewHolder holder) { return false; } @Override public boolean animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY) { Log.d(LOGTAG, String.format("animateMove")); return false; } @Override public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { Log.d(LOGTAG, String.format("animateChange")); return false; } @Override public void endAnimation(ViewHolder item) { Log.d(LOGTAG, String.format("endAnimation")); } @Override public void endAnimations() { } @Override public boolean isRunning() { return false; } }); mAccountsAdapter = new AccountIconsAdapter(this); mAccountSelector.setAdapter(mAccountsAdapter); mAccountsAdapter.setAccounts(ParcelableAccount.getAccounts(this, false, false)); mMediaPreviewAdapter = new MediaPreviewAdapter(this); mMediaPreviewGrid.setAdapter(mMediaPreviewAdapter); final Intent intent = getIntent(); if (savedInstanceState != null) { // Restore from previous saved state mAccountsAdapter.setSelectedAccountIds(savedInstanceState.getLongArray(EXTRA_ACCOUNT_IDS)); mIsPossiblySensitive = savedInstanceState.getBoolean(EXTRA_IS_POSSIBLY_SENSITIVE); final ArrayList<ParcelableMediaUpdate> mediaList = savedInstanceState .getParcelableArrayList(EXTRA_MEDIA); if (mediaList != null) { addMedia(mediaList); } mInReplyToStatus = savedInstanceState.getParcelable(EXTRA_STATUS); mInReplyToStatusId = savedInstanceState.getLong(EXTRA_STATUS_ID); mMentionUser = savedInstanceState.getParcelable(EXTRA_USER); mDraftItem = savedInstanceState.getParcelable(EXTRA_DRAFT); mShouldSaveAccounts = savedInstanceState.getBoolean(EXTRA_SHOULD_SAVE_ACCOUNTS); mOriginalText = savedInstanceState.getString(EXTRA_ORIGINAL_TEXT); } else { // The context was first created final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); final long notificationAccount = intent.getLongExtra(EXTRA_NOTIFICATION_ACCOUNT, -1); if (notificationId != -1) { mTwitterWrapper.clearNotificationAsync(notificationId, notificationAccount); } if (!handleIntent(intent)) { handleDefaultIntent(intent); } final long[] accountIds = mAccountsAdapter.getSelectedAccountIds(); if (accountIds.length == 0) { final long[] idsInPrefs = TwidereArrayUtils .parseLongArray(mPreferences.getString(KEY_COMPOSE_ACCOUNTS, null), ','); final long[] intersection = TwidereArrayUtils.intersection(idsInPrefs, defaultAccountIds); mAccountsAdapter.setSelectedAccountIds(intersection.length > 0 ? intersection : defaultAccountIds); } mOriginalText = ParseUtils.parseString(mEditText.getText()); } if (!setComposeTitle(intent)) { setTitle(R.string.compose); } final Menu menu = mMenuBar.getMenu(); getMenuInflater().inflate(R.menu.menu_compose, menu); ThemeUtils.wrapMenuIcon(mMenuBar); mSendView.setOnClickListener(this); mSendView.setOnLongClickListener(this); final Intent composeExtensionsIntent = new Intent(INTENT_ACTION_EXTENSION_COMPOSE); Utils.addIntentToMenu(this, menu, composeExtensionsIntent, MENU_GROUP_COMPOSE_EXTENSION); final Intent imageExtensionsIntent = new Intent(INTENT_ACTION_EXTENSION_EDIT_IMAGE); final MenuItem mediaMenuItem = menu.findItem(R.id.media_menu); if (mediaMenuItem != null && mediaMenuItem.hasSubMenu()) { Utils.addIntentToMenu(this, mediaMenuItem.getSubMenu(), imageExtensionsIntent, MENU_GROUP_IMAGE_EXTENSION); } setMenu(); updateLocationState(); updateMediaPreview(); notifyAccountSelectionChanged(); mTextChanged = false; } private void setupEditText() { final boolean sendByEnter = mPreferences.getBoolean(KEY_QUICK_SEND); EditTextEnterHandler.attach(mEditText, new EnterListener() { @Override public void onHitEnter() { updateStatus(); } }, sendByEnter); mEditText.addTextChangedListener(new TextWatcher() { @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) { setMenu(); updateTextCount(); if (s instanceof Spannable && count == 1 && before == 0) { final ImageSpan[] imageSpans = ((Spannable) s).getSpans(start, start + count, ImageSpan.class); if (imageSpans.length == 1) { final Intent intent = new Intent(ComposeActivity.this, ImagePickerActivity.class); intent.setAction(ImagePickerActivity.INTENT_ACTION_GET_IMAGE); intent.setData(Uri.parse(imageSpans[0].getSource())); startActivityForResult(intent, REQUEST_PICK_IMAGE); ((Spannable) s).setSpan(new MarkForDeleteSpan(), start, start + count, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } } } @Override public void afterTextChanged(final Editable s) { mTextChanged = s.length() == 0; final MarkForDeleteSpan[] deletes = s.getSpans(0, s.length(), MarkForDeleteSpan.class); for (MarkForDeleteSpan delete : deletes) { s.delete(s.getSpanStart(delete), s.getSpanEnd(delete)); } } }); mEditText.setCustomSelectionActionModeCallback(this); } @Override protected void onTitleChanged(final CharSequence title, final int color) { super.onTitleChanged(title, color); } @Override public boolean handleKeyboardShortcutSingle(@NonNull KeyboardShortcutsHandler handler, int keyCode, @NonNull KeyEvent event) { final String action = handler.getKeyAction(CONTEXT_TAG_NAVIGATION, keyCode, event); if (ACTION_NAVIGATION_BACK.equals(action)) { if (mEditText.length() == 0 && !mTextChanged) { if (!mNavigateBackPressed) { final SuperToast toast = SuperToast.create(this, getString(R.string.press_again_to_close), Duration.SHORT); toast.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(View view) { mNavigateBackPressed = false; } }); toast.show(); } else { onBackPressed(); } mNavigateBackPressed = true; } else { mTextChanged = false; } return true; } return super.handleKeyboardShortcutSingle(handler, keyCode, event); } @Override public boolean handleKeyboardShortcutRepeat(@NonNull KeyboardShortcutsHandler handler, int keyCode, int repeatCount, @NonNull KeyEvent event) { return super.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event); } private void addMedia(final ParcelableMediaUpdate media) { mMediaPreviewAdapter.add(media); updateMediaPreview(); } private void addMedia(final List<ParcelableMediaUpdate> media) { mMediaPreviewAdapter.addAll(media); updateMediaPreview(); } private void clearMedia() { mMediaPreviewAdapter.clear(); updateMediaPreview(); } private Uri createTempImageUri() { final File file = new File(getCacheDir(), "tmp_image_" + System.currentTimeMillis()); return Uri.fromFile(file); } private void displayNewDraftNotification(String text, Uri draftUri) { final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); final NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setTicker(getString(R.string.draft_saved)); builder.setContentTitle(getString(R.string.draft_saved)); if (TextUtils.isEmpty(text)) { builder.setContentText(getString(R.string.empty_content)); } else { builder.setContentText(text); } builder.setSmallIcon(R.drawable.ic_stat_info); builder.setAutoCancel(true); final Intent draftsIntent = new Intent(this, DraftsFragment.class); builder.setContentIntent( PendingIntent.getActivity(this, 0, draftsIntent, PendingIntent.FLAG_UPDATE_CURRENT)); final Intent serviceIntent = new Intent(this, BackgroundOperationService.class); serviceIntent.setAction(INTENT_ACTION_DISCARD_DRAFT); serviceIntent.setData(draftUri); final Action.Builder actionBuilder = new Action.Builder(R.drawable.ic_action_delete, getString(R.string.discard), PendingIntent.getService(this, 0, serviceIntent, PendingIntent.FLAG_UPDATE_CURRENT)); builder.addAction(actionBuilder.build()); nm.notify(draftUri.toString(), NOTIFICATION_ID_DRAFTS, builder.build()); } private ParcelableMediaUpdate[] getMedia() { final List<ParcelableMediaUpdate> list = getMediaList(); return list.toArray(new ParcelableMediaUpdate[list.size()]); } private List<ParcelableMediaUpdate> getMediaList() { return mMediaPreviewAdapter.getAsList(); } private boolean handleDefaultIntent(final Intent intent) { if (intent == null) return false; final String action = intent.getAction(); final boolean hasAccountIds; if (intent.hasExtra(EXTRA_ACCOUNT_IDS)) { mAccountsAdapter.setSelectedAccountIds(intent.getLongArrayExtra(EXTRA_ACCOUNT_IDS)); hasAccountIds = true; } else if (intent.hasExtra(EXTRA_ACCOUNT_ID)) { mAccountsAdapter.setSelectedAccountIds(intent.getLongExtra(EXTRA_ACCOUNT_ID, -1)); hasAccountIds = true; } else { hasAccountIds = false; } mShouldSaveAccounts = !Intent.ACTION_SEND.equals(action) && !Intent.ACTION_SEND_MULTIPLE.equals(action) && !hasAccountIds; final Uri data = intent.getData(); final CharSequence extraSubject = intent.getCharSequenceExtra(Intent.EXTRA_SUBJECT); final CharSequence extraText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT); final Uri extraStream = intent.getParcelableExtra(Intent.EXTRA_STREAM); //TODO handle share_screenshot extra (Bitmap) if (extraStream != null) { AsyncTaskUtils.executeTask( new AddMediaTask(this, extraStream, createTempImageUri(), ParcelableMedia.TYPE_IMAGE, false)); } else if (data != null) { AsyncTaskUtils.executeTask( new AddMediaTask(this, data, createTempImageUri(), ParcelableMedia.TYPE_IMAGE, false)); } else if (intent.hasExtra(EXTRA_SHARE_SCREENSHOT) && Utils.useShareScreenshot()) { final Bitmap bitmap = intent.getParcelableExtra(EXTRA_SHARE_SCREENSHOT); if (bitmap != null) { try { AsyncTaskUtils.executeTask( new AddBitmapTask(this, bitmap, createTempImageUri(), ParcelableMedia.TYPE_IMAGE)); } catch (IOException e) { // ignore bitmap.recycle(); } } } mEditText.setText(Utils.getShareStatus(this, extraSubject, extraText)); 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 selectionEnd = mEditText.length(); mEditText.setSelection(selectionEnd); mAccountsAdapter.setSelectedAccountIds(draft.account_ids); if (draft.media != null) { addMedia(Arrays.asList(draft.media)); } mIsPossiblySensitive = draft.is_possibly_sensitive; mInReplyToStatusId = draft.in_reply_to_status_id; return true; } private boolean handleIntent(final Intent intent) { final String action = intent.getAction(); if (action == null) return false; mShouldSaveAccounts = false; mMentionUser = intent.getParcelableExtra(EXTRA_USER); mInReplyToStatus = intent.getParcelableExtra(EXTRA_STATUS); mInReplyToStatusId = mInReplyToStatus != null ? mInReplyToStatus.id : -1; switch (action) { case INTENT_ACTION_REPLY: { return handleReplyIntent(mInReplyToStatus); } case INTENT_ACTION_QUOTE: { return handleQuoteIntent(mInReplyToStatus); } case INTENT_ACTION_EDIT_DRAFT: { mDraftItem = intent.getParcelableExtra(EXTRA_DRAFT); return handleEditDraftIntent(mDraftItem); } case INTENT_ACTION_MENTION: { return handleMentionIntent(mMentionUser); } case INTENT_ACTION_REPLY_MULTIPLE: { final String[] screenNames = intent.getStringArrayExtra(EXTRA_SCREEN_NAMES); final long accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1); final long inReplyToUserId = intent.getLongExtra(EXTRA_IN_REPLY_TO_ID, -1); return handleReplyMultipleIntent(screenNames, accountId, inReplyToUserId); } case INTENT_ACTION_COMPOSE_TAKE_PHOTO: { return takePhoto(); } case INTENT_ACTION_COMPOSE_PICK_IMAGE: { return pickImage(); } } // Unknown action or no intent extras return false; } private boolean handleMentionIntent(final ParcelableUser user) { if (user == null || user.id <= 0) return false; final String my_screen_name = Utils.getAccountScreenName(this, user.account_id); if (TextUtils.isEmpty(my_screen_name)) return false; mEditText.setText("@" + user.screen_name + " "); final int selection_end = mEditText.length(); mEditText.setSelection(selection_end); mAccountsAdapter.setSelectedAccountIds(user.account_id); return true; } private boolean handleQuoteIntent(final ParcelableStatus status) { if (status == null || status.id <= 0) return false; mEditText.setText(Utils.getQuoteStatus(this, status.id, status.user_screen_name, status.text_plain)); mEditText.setSelection(0); mAccountsAdapter.setSelectedAccountIds(status.account_id); return true; } private boolean handleReplyIntent(final ParcelableStatus status) { if (status == null || status.id <= 0) return false; final String myScreenName = Utils.getAccountScreenName(this, status.account_id); if (TextUtils.isEmpty(myScreenName)) return false; int selectionStart = 0; if (status.is_quote) { mEditText.append("@" + status.quoted_by_user_screen_name + " "); selectionStart = mEditText.length(); } mEditText.append("@" + status.user_screen_name + " "); if (!status.is_quote) { selectionStart = mEditText.length(); } if (status.is_retweet) { mEditText.append("@" + status.retweeted_by_user_screen_name + " "); } final Collection<String> mentions = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); mentions.addAll(mExtractor.extractMentionedScreennames(status.text_plain)); for (final String mention : mentions) { if (mention.equalsIgnoreCase(status.user_screen_name) || mention.equalsIgnoreCase(myScreenName) || mention.equalsIgnoreCase(status.retweeted_by_user_screen_name)) { continue; } mEditText.append("@" + mention + " "); } final int selectionEnd = mEditText.length(); mEditText.setSelection(selectionStart, selectionEnd); mAccountsAdapter.setSelectedAccountIds(status.account_id); return true; } private boolean handleReplyMultipleIntent(final String[] screenNames, final long accountId, final long inReplyToStatusId) { if (screenNames == null || screenNames.length == 0 || accountId <= 0) return false; final String myScreenName = Utils.getAccountScreenName(this, accountId); if (TextUtils.isEmpty(myScreenName)) return false; for (final String screenName : screenNames) { if (screenName.equalsIgnoreCase(myScreenName)) { continue; } mEditText.append("@" + screenName + " "); } mEditText.setSelection(mEditText.length()); mAccountsAdapter.setSelectedAccountIds(accountId); mInReplyToStatusId = inReplyToStatusId; return true; } private boolean hasMedia() { return !mMediaPreviewAdapter.isEmpty(); } private boolean isQuote() { return INTENT_ACTION_QUOTE.equals(getIntent().getAction()); } private boolean isQuotingProtectedStatus() { if (!isQuote() || mInReplyToStatus == null) return false; return mInReplyToStatus.user_is_protected && mInReplyToStatus.account_id != mInReplyToStatus.user_id; } private boolean noReplyContent(final String text) { if (text == null) return true; final String action = getIntent().getAction(); final boolean is_reply = INTENT_ACTION_REPLY.equals(action) || INTENT_ACTION_REPLY_MULTIPLE.equals(action); return is_reply && text.equals(mOriginalText); } private void notifyAccountSelectionChanged() { final ParcelableAccount[] accounts = mAccountsAdapter.getSelectedAccounts(); setSelectedAccounts(accounts); mEditText.setAccountId(accounts.length > 0 ? accounts[0].account_id : Utils.getDefaultAccountId(this)); // mAccountActionProvider.setSelectedAccounts(mAccountsAdapter.getSelectedAccounts()); } @TargetApi(Build.VERSION_CODES.KITKAT) private boolean openDocument() { final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); final String[] mimeTypes = { "image/png", "image/jpeg", "image/gif" }; intent.setType("image/*"); intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivityForResult(intent, REQUEST_OPEN_DOCUMENT); } catch (final ActivityNotFoundException e) { return false; } return true; } private boolean pickImage() { final Intent intent = new Intent(this, ImagePickerActivity.class); intent.setAction(ImagePickerActivity.INTENT_ACTION_PICK_IMAGE); startActivityForResult(intent, REQUEST_PICK_IMAGE); return true; } private void saveAccountSelection() { if (!mShouldSaveAccounts) return; final SharedPreferences.Editor editor = mPreferences.edit(); editor.putString(KEY_COMPOSE_ACCOUNTS, TwidereArrayUtils.toString(mAccountsAdapter.getSelectedAccountIds(), ',', false)); editor.apply(); } private void setAccountSelectorVisible(boolean visible) { mAccountSelectorContainer.setVisibility(visible ? View.VISIBLE : View.GONE); } private boolean setComposeTitle(final Intent intent) { final String action = intent.getAction(); final UserColorNameManager manager = UserColorNameManager.getInstance(this); final boolean nameFirst = mPreferences.getBoolean(KEY_NAME_FIRST); if (INTENT_ACTION_REPLY.equals(action)) { if (mInReplyToStatus == null) return false; final String displayName = manager.getDisplayName(mInReplyToStatus.user_id, mInReplyToStatus.user_name, mInReplyToStatus.user_screen_name, nameFirst, false); setTitle(getString(R.string.reply_to, displayName)); } else if (INTENT_ACTION_QUOTE.equals(action)) { if (mInReplyToStatus == null) return false; final String displayName = manager.getDisplayName(mInReplyToStatus.user_id, mInReplyToStatus.user_name, mInReplyToStatus.user_screen_name, nameFirst, false); setTitle(getString(R.string.quote_user, displayName)); } else if (INTENT_ACTION_EDIT_DRAFT.equals(action)) { if (mDraftItem == null) return false; setTitle(R.string.edit_draft); } else if (INTENT_ACTION_MENTION.equals(action)) { if (mMentionUser == null) return false; final String displayName = manager.getDisplayName(mMentionUser.id, mMentionUser.name, mMentionUser.screen_name, nameFirst, false); setTitle(getString(R.string.mention_user, displayName)); } else if (INTENT_ACTION_REPLY_MULTIPLE.equals(action)) { setTitle(R.string.reply); } else if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) { setTitle(R.string.share); } else { setTitle(R.string.compose); } return true; } private void setMenu() { if (mMenuBar == null) return; final Menu menu = mMenuBar.getMenu(); final boolean hasMedia = hasMedia(), hasInReplyTo = mInReplyToStatus != null; /* * No media & Not reply: [Take photo][Add image][Attach location][Drafts] * Has media & Not reply: [Take photo][Media menu][Attach location][Drafts] * Is reply: [Media menu][View status][Attach location][Drafts] */ MenuUtils.setMenuItemAvailability(menu, MENU_TAKE_PHOTO, !hasInReplyTo); MenuUtils.setMenuItemAvailability(menu, R.id.take_photo_sub_item, hasInReplyTo); MenuUtils.setMenuItemAvailability(menu, MENU_ADD_IMAGE, !hasMedia && !hasInReplyTo); MenuUtils.setMenuItemAvailability(menu, MENU_VIEW, hasInReplyTo); MenuUtils.setMenuItemAvailability(menu, R.id.media_menu, hasMedia || hasInReplyTo); MenuUtils.setMenuItemAvailability(menu, MENU_TOGGLE_SENSITIVE, hasMedia); MenuUtils.setMenuItemAvailability(menu, MENU_EDIT_MEDIA, hasMedia); MenuUtils.setMenuItemAvailability(menu, R.id.link_to_quoted_status, isQuote()); menu.setGroupEnabled(MENU_GROUP_IMAGE_EXTENSION, hasMedia); menu.setGroupVisible(MENU_GROUP_IMAGE_EXTENSION, hasMedia); MenuUtils.setMenuItemChecked(menu, MENU_TOGGLE_SENSITIVE, hasMedia && mIsPossiblySensitive); MenuUtils.setMenuItemChecked(menu, R.id.link_to_quoted_status, mPreferences.getBoolean(KEY_LINK_TO_QUOTED_TWEET)); ThemeUtils.resetCheatSheet(mMenuBar); // mMenuBar.show(); } private void setProgressVisible(final boolean visible) { // mProgress.setVisibility(visible ? View.VISIBLE : View.GONE); } private void setRecentLocation(ParcelableLocation location) { if (location != null) { mLocationText.setText(location.getHumanReadableString(3)); } else { mLocationText.setText(R.string.unknown_location); } mRecentLocation = location; } /** * The Location Manager manages location providers. This code searches for * the best provider of data (GPS, WiFi/cell phone tower lookup, some other * mechanism) and finds the last known location. */ private boolean startLocationUpdateIfEnabled() { final LocationManager lm = mLocationManager; final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION); if (!attachLocation) { lm.removeUpdates(this); return false; } final Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); final String provider = lm.getBestProvider(criteria, true); if (provider != null) { mLocationText.setText(R.string.getting_location); lm.requestLocationUpdates(provider, 0, 0, this); final Location location; if (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { location = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } else { location = lm.getLastKnownLocation(provider); } if (location != null) { onLocationChanged(location); } } else { Toast.makeText(this, R.string.cannot_get_location, Toast.LENGTH_SHORT).show(); } return provider != null; } private boolean takePhoto() { final Intent intent = new Intent(this, ImagePickerActivity.class); intent.setAction(ImagePickerActivity.INTENT_ACTION_TAKE_PHOTO); startActivityForResult(intent, REQUEST_TAKE_PHOTO); return true; } private void toggleLocation() { final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION, false); mPreferences.edit().putBoolean(KEY_ATTACH_LOCATION, !attachLocation).apply(); startLocationUpdateIfEnabled(); updateLocationState(); setMenu(); updateTextCount(); } private void updateLocationState() { final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION, false); if (attachLocation) { mLocationIcon.setColorFilter(ThemeUtils.getOptimalAccentColor(this, false, getCurrentThemeResourceId()), Mode.SRC_ATOP); } else { mLocationIcon.setColorFilter(mLocationIcon.getDefaultColor(), Mode.SRC_ATOP); mLocationText.setText(R.string.no_location); } } private void updateMediaPreview() { final int count = mMediaPreviewAdapter.getCount(); final Resources res = getResources(); final int maxColumns = res.getInteger(R.integer.grid_column_image_preview); mMediaPreviewGrid.setNumColumns(MathUtils.clamp(count, maxColumns, 1)); } private void updateStatus() { if (isFinishing()) return; final boolean hasMedia = hasMedia(); final String text = mEditText != null ? ParseUtils.parseString(mEditText.getText()) : null; final int tweetLength = mValidator.getTweetLength(text), maxLength = mValidator.getMaxTweetLength(); if (!mStatusShortenerUsed && tweetLength > maxLength) { mEditText.setError(getString(R.string.error_message_status_too_long)); final int textLength = mEditText.length(); mEditText.setSelection(textLength - (tweetLength - maxLength), textLength); return; } else if (!hasMedia && (TextUtils.isEmpty(text) || noReplyContent(text))) { mEditText.setError(getString(R.string.error_message_no_content)); return; } else if (mAccountsAdapter.isSelectionEmpty()) { mEditText.setError(getString(R.string.no_account_selected)); return; } final boolean attachLocation = mPreferences.getBoolean(KEY_ATTACH_LOCATION, false); // if (mRecentLocation == null && attachLocation) { // final Location location; // if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { // location = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); // } else { // location = null; // } // if (location != null) { // mRecentLocation = new ParcelableLocation(location); // } // setRecentLocation(); // } final long[] accountIds = mAccountsAdapter.getSelectedAccountIds(); final boolean isQuote = isQuote(); final ParcelableLocation statusLocation = attachLocation ? mRecentLocation : null; final boolean linkToQuotedTweet = mPreferences.getBoolean(KEY_LINK_TO_QUOTED_TWEET, true); final long inReplyToStatusId = !isQuote || linkToQuotedTweet ? mInReplyToStatusId : -1; final boolean isPossiblySensitive = hasMedia && mIsPossiblySensitive; mTwitterWrapper.updateStatusAsync(accountIds, text, statusLocation, getMedia(), inReplyToStatusId, isPossiblySensitive); if (mPreferences.getBoolean(KEY_NO_CLOSE_AFTER_TWEET_SENT, false) && (mInReplyToStatus == null || mInReplyToStatusId <= 0)) { mIsPossiblySensitive = false; mShouldSaveAccounts = true; mInReplyToStatus = null; mMentionUser = null; mDraftItem = null; mInReplyToStatusId = -1; mOriginalText = null; mEditText.setText(null); clearMedia(); final Intent intent = new Intent(INTENT_ACTION_COMPOSE); setIntent(intent); setComposeTitle(intent); handleIntent(intent); setMenu(); updateTextCount(); } else { setResult(Activity.RESULT_OK); finish(); } } private void updateTextCount() { if (mSendTextCountView == null || mEditText == null) return; final String textOrig = ParseUtils.parseString(mEditText.getText()); final String text = hasMedia() && textOrig != null ? mImageUploaderUsed ? Utils.getImageUploadStatus(this, new String[] { FAKE_IMAGE_LINK }, textOrig) : textOrig + " " + FAKE_IMAGE_LINK : textOrig; final int validatedCount = text != null ? mValidator.getTweetLength(text) : 0; mSendTextCountView.setTextCount(validatedCount); } static class AccountIconViewHolder extends ViewHolder implements OnClickListener { private final AccountIconsAdapter adapter; private final ShapedImageView iconView; private final TextView nameView; public AccountIconViewHolder(AccountIconsAdapter adapter, View itemView) { super(itemView); this.adapter = adapter; iconView = (ShapedImageView) itemView.findViewById(android.R.id.icon); nameView = (TextView) itemView.findViewById(android.R.id.text1); itemView.setOnClickListener(this); } public void showAccount(AccountIconsAdapter adapter, ParcelableAccount account, boolean isSelected) { itemView.setAlpha(isSelected ? 1 : 0.33f); final MediaLoaderWrapper loader = adapter.getImageLoader(); loader.displayProfileImage(iconView, account.profile_image_url); iconView.setBorderColor(account.color); nameView.setText(adapter.isNameFirst() ? account.name : ("@" + account.screen_name)); } @Override public void onClick(View v) { adapter.toggleSelection(getAdapterPosition()); } } private static class AccountIconsAdapter extends Adapter<AccountIconViewHolder> { private final ComposeActivity mActivity; private final LayoutInflater mInflater; private final MediaLoaderWrapper mImageLoader; private final LongSparseArray<Boolean> mSelection; private final boolean mNameFirst; private ParcelableAccount[] mAccounts; public AccountIconsAdapter(ComposeActivity activity) { setHasStableIds(true); mActivity = activity; mInflater = activity.getLayoutInflater(); mImageLoader = TwidereApplication.getInstance(activity).getMediaLoaderWrapper(); mSelection = new LongSparseArray<>(); final SharedPreferencesWrapper preferences = SharedPreferencesWrapper.getInstance(activity, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE, SharedPreferenceConstants.class); mNameFirst = preferences.getBoolean(KEY_NAME_FIRST); } public MediaLoaderWrapper getImageLoader() { return mImageLoader; } @NonNull public long[] getSelectedAccountIds() { if (mAccounts == null) return new long[0]; final long[] temp = new long[mAccounts.length]; int selectedCount = 0; for (ParcelableAccount account : mAccounts) { if (mSelection.get(account.account_id, false)) { temp[selectedCount++] = account.account_id; } } final long[] result = new long[selectedCount]; System.arraycopy(temp, 0, result, 0, result.length); return result; } public void setSelectedAccountIds(long... accountIds) { mSelection.clear(); if (accountIds != null) { for (long accountId : accountIds) { mSelection.put(accountId, true); } } notifyDataSetChanged(); } @NonNull public ParcelableAccount[] getSelectedAccounts() { if (mAccounts == null) return new ParcelableAccount[0]; final ParcelableAccount[] temp = new ParcelableAccount[mAccounts.length]; int selectedCount = 0; for (ParcelableAccount account : mAccounts) { if (mSelection.get(account.account_id, false)) { temp[selectedCount++] = account; } } final ParcelableAccount[] result = new ParcelableAccount[selectedCount]; System.arraycopy(temp, 0, result, 0, result.length); return result; } public boolean isSelectionEmpty() { return getSelectedAccountIds().length == 0; } @Override public AccountIconViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final View view = mInflater.inflate(R.layout.adapter_item_compose_account, parent, false); return new AccountIconViewHolder(this, view); } @Override public void onBindViewHolder(AccountIconViewHolder holder, int position) { final ParcelableAccount account = mAccounts[position]; final boolean isSelected = mSelection.get(account.account_id, false); holder.showAccount(this, account, isSelected); } @Override public int getItemCount() { return mAccounts != null ? mAccounts.length : 0; } public void setAccounts(ParcelableAccount[] accounts) { mAccounts = accounts; notifyDataSetChanged(); } private void toggleSelection(int position) { if (mAccounts == null) return; final long accountId = mAccounts[position].account_id; mSelection.put(accountId, !mSelection.get(accountId, false)); mActivity.notifyAccountSelectionChanged(); notifyDataSetChanged(); } public boolean isNameFirst() { return mNameFirst; } } private static class AddBitmapTask extends AddMediaTask { private final Bitmap mBitmap; AddBitmapTask(ComposeActivity activity, Bitmap bitmap, Uri dst, int media_type) throws IOException { super(activity, Uri.fromFile(File.createTempFile("tmp_bitmap", null)), dst, media_type, true); mBitmap = bitmap; } @Override protected Boolean doInBackground(Object... params) { if (mBitmap == null || mBitmap.isRecycled()) return false; FileOutputStream os = null; try { os = new FileOutputStream(getSrc().getPath()); mBitmap.compress(Bitmap.CompressFormat.PNG, 0, os); mBitmap.recycle(); } catch (IOException e) { return false; } finally { IoUtils.closeSilently(os); } return super.doInBackground(params); } } private static class AddMediaTask extends AsyncTask<Object, Object, Boolean> { private final ComposeActivity activity; private final int media_type; private final Uri src, dst; private final boolean delete_src; AddMediaTask(final ComposeActivity activity, final Uri src, final Uri dst, final int media_type, final boolean delete_src) { this.activity = activity; this.src = src; this.dst = dst; this.media_type = media_type; this.delete_src = delete_src; } @Override protected Boolean doInBackground(final Object... params) { InputStream is = null; OutputStream os = null; try { final ContentResolver resolver = activity.getContentResolver(); is = resolver.openInputStream(src); os = resolver.openOutputStream(dst); Utils.copyStream(is, os); if (ContentResolver.SCHEME_FILE.equals(src.getScheme()) && delete_src) { final File file = new File(src.getPath()); if (!file.delete()) { Log.d(LOGTAG, String.format("Unable to delete %s", file)); } } } catch (final IOException e) { Log.w(LOGTAG, e); return false; } finally { Utils.closeSilently(os); Utils.closeSilently(is); } return true; } Uri getSrc() { return src; } @Override protected void onPostExecute(final Boolean result) { activity.setProgressVisible(false); activity.addMedia(new ParcelableMediaUpdate(dst.toString(), media_type)); activity.setMenu(); activity.updateTextCount(); if (!result) { Toast.makeText(activity, R.string.error_occurred, Toast.LENGTH_SHORT).show(); } } @Override protected void onPreExecute() { activity.setProgressVisible(true); } } private static class DeleteImageTask extends AsyncTask<Object, Object, Boolean> { final ComposeActivity mActivity; private final ParcelableMediaUpdate[] mMedia; DeleteImageTask(final ComposeActivity activity, final ParcelableMediaUpdate... media) { this.mActivity = activity; this.mMedia = media; } @Override protected Boolean doInBackground(final Object... params) { if (mMedia == null) return false; try { for (final ParcelableMediaUpdate media : mMedia) { if (media.uri == null) continue; final Uri uri = Uri.parse(media.uri); if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { final File file = new File(uri.getPath()); if (!file.delete()) { Log.d(LOGTAG, String.format("Unable to delete %s", file)); } } } } catch (final Exception e) { return false; } return true; } @Override protected void onPostExecute(final Boolean result) { mActivity.setProgressVisible(false); mActivity.removeAllMedia(Arrays.asList(mMedia)); mActivity.setMenu(); if (!result) { Toast.makeText(mActivity, R.string.error_occurred, Toast.LENGTH_SHORT).show(); } } @Override protected void onPreExecute() { mActivity.setProgressVisible(true); } } private static class DiscardTweetTask extends AsyncTask<Object, Object, Object> { final ComposeActivity mActivity; DiscardTweetTask(final ComposeActivity activity) { this.mActivity = activity; } @Override protected Object doInBackground(final Object... params) { for (final ParcelableMediaUpdate media : mActivity.getMediaList()) { if (media.uri == null) continue; final Uri uri = Uri.parse(media.uri); if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { final File file = new File(uri.getPath()); if (!file.delete()) { Log.d(LOGTAG, String.format("Unable to delete %s", file)); } } } return null; } @Override protected void onPostExecute(final Object result) { mActivity.setProgressVisible(false); mActivity.finish(); } @Override protected void onPreExecute() { mActivity.setProgressVisible(true); } } private static class MediaPreviewAdapter extends DraggableArrayAdapter<ParcelableMediaUpdate> { private final MediaLoaderWrapper mImageLoader; public MediaPreviewAdapter(final Context context) { super(context, R.layout.grid_item_media_editor); mImageLoader = TwidereApplication.getInstance(context).getMediaLoaderWrapper(); } public List<ParcelableMediaUpdate> getAsList() { return Collections.unmodifiableList(getObjects()); } @Override public View getView(final int position, final View convertView, final ViewGroup parent) { final View view = super.getView(position, convertView, parent); final ParcelableMediaUpdate media = getItem(position); final ImageView image = (ImageView) view.findViewById(R.id.image); mImageLoader.displayPreviewImage(media.uri, image); return view; } } public static class RetweetProtectedStatusWarnFragment extends BaseSupportDialogFragment implements DialogInterface.OnClickListener { @Override public void onClick(final DialogInterface dialog, final int which) { final Activity activity = getActivity(); switch (which) { case DialogInterface.BUTTON_POSITIVE: { if (activity instanceof ComposeActivity) { ((ComposeActivity) activity).updateStatus(); } break; } } } @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { final Context wrapped = ThemeUtils.getDialogThemedContext(getActivity()); final AlertDialog.Builder builder = new AlertDialog.Builder(wrapped); builder.setMessage(R.string.quote_protected_status_warning_message); builder.setPositiveButton(R.string.send_anyway, this); builder.setNegativeButton(android.R.string.cancel, null); return builder.create(); } } private static class SpacingItemDecoration extends ItemDecoration { private final int mSpacingSmall, mSpacingNormal; SpacingItemDecoration(Context context) { final Resources resources = context.getResources(); mSpacingSmall = resources.getDimensionPixelSize(R.dimen.element_spacing_small); mSpacingNormal = resources.getDimensionPixelSize(R.dimen.element_spacing_normal); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { final int pos = parent.getChildAdapterPosition(view); if (pos == 0) { outRect.set(0, mSpacingNormal, 0, 0); } else if (pos == parent.getAdapter().getItemCount() - 1) { outRect.set(0, 0, 0, mSpacingNormal); } else { outRect.set(0, 0, 0, 0); } } } }