diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 02ff82780..fcb8f3e19 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -149,6 +149,7 @@ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - + - + + + + + + + + + + + + + + + + - + diff --git a/res/layout/share_fragment.xml b/res/layout/share_fragment.xml deleted file mode 100644 index 10f692b5a..000000000 --- a/res/layout/share_fragment.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/res/layout/share_list_item_view.xml b/res/layout/share_list_item_view.xml deleted file mode 100644 index 0157b2b54..000000000 --- a/res/layout/share_list_item_view.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 2d8cb341b..305fefef5 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -8,13 +8,10 @@ - - - diff --git a/src/org/thoughtcrime/securesms/ShareActivity.java b/src/org/thoughtcrime/securesms/ShareActivity.java index 4fd25d48b..c64ceca5c 100644 --- a/src/org/thoughtcrime/securesms/ShareActivity.java +++ b/src/org/thoughtcrime/securesms/ShareActivity.java @@ -1,5 +1,5 @@ -/** - * Copyright (C) 2014 Open Whisper Systems +/* + * Copyright (C) 2014-2017 Open Whisper Systems * * 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 @@ -17,6 +17,7 @@ package org.thoughtcrime.securesms; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -28,22 +29,28 @@ import android.os.Process; import android.provider.OpenableColumns; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.Toolbar; import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; +import android.widget.ImageView; +import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.Address; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; import java.io.FileInputStream; @@ -56,7 +63,7 @@ import java.io.InputStream; * @author Jake McGinty */ public class ShareActivity extends PassphraseRequiredActionBarActivity - implements ShareFragment.ConversationSelectedListener + implements ContactSelectionListFragment.OnContactSelectedListener, SwipeRefreshLayout.OnRefreshListener { private static final String TAG = ShareActivity.class.getSimpleName(); @@ -64,15 +71,17 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity public static final String EXTRA_ADDRESS_MARSHALLED = "address_marshalled"; public static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type"; - private final DynamicTheme dynamicTheme = new DynamicTheme (); + private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - private MasterSecret masterSecret; - private ViewGroup fragmentContainer; - private View progressWheel; - private Uri resolvedExtra; - private String mimeType; - private boolean isPassingAlongMedia; + private MasterSecret masterSecret; + private ContactSelectionListFragment contactsFragment; + private SearchToolbar searchToolbar; + private ImageView searchAction; + private View progressWheel; + private Uri resolvedExtra; + private String mimeType; + private boolean isPassingAlongMedia; @Override protected void onPreCreate() { @@ -83,12 +92,19 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity @Override protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) { this.masterSecret = masterSecret; + + if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { + getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, + TextSecurePreferences.isSmsEnabled(this) + ? ContactSelectionListFragment.DISPLAY_MODE_ALL + : ContactSelectionListFragment.DISPLAY_MODE_PUSH_ONLY); + } + setContentView(R.layout.share_activity); - fragmentContainer = ViewUtil.findById(this, R.id.drawer_layout); - progressWheel = ViewUtil.findById(this, R.id.progress_wheel); - - initFragment(R.id.drawer_layout, new ShareFragment(), masterSecret); + initializeToolbar(); + initializeResources(); + initializeSearch(); initializeMedia(); } @@ -106,7 +122,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity super.onResume(); dynamicTheme.onResume(this); dynamicLanguage.onResume(this); - getSupportActionBar().setTitle(R.string.ShareActivity_share_with); } @Override @@ -120,6 +135,53 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity } } + @Override + public void onBackPressed() { + if (searchToolbar.isVisible()) searchToolbar.collapse(); + else super.onBackPressed(); + } + + private void initializeToolbar() { + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } + + private void initializeResources() { + progressWheel = findViewById(R.id.progress_wheel); + searchToolbar = findViewById(R.id.search_toolbar); + searchAction = findViewById(R.id.search_action); + contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment); + contactsFragment.setOnContactSelectedListener(this); + contactsFragment.setOnRefreshListener(this); + } + + private void initializeSearch() { + searchAction.setOnClickListener(v -> searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2), + searchAction.getY() + (searchAction.getHeight() / 2))); + + searchToolbar.setListener(new SearchToolbar.SearchListener() { + @Override + public void onSearchTextChange(String text) { + if (contactsFragment != null) { + contactsFragment.setQueryFilter(text); + } + } + + @Override + public void onSearchReset() { + if (contactsFragment != null) { + contactsFragment.resetQueryFilter(); + } + } + }); + } + private void initializeMedia() { final Context context = this; isPassingAlongMedia = false; @@ -132,22 +194,12 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity resolvedExtra = streamExtra; handleResolvedMedia(getIntent(), false); } else { - fragmentContainer.setVisibility(View.GONE); + contactsFragment.getView().setVisibility(View.GONE); progressWheel.setVisibility(View.VISIBLE); new ResolveMediaTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, streamExtra); } } - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuInflater inflater = this.getMenuInflater(); - menu.clear(); - - inflater.inflate(R.menu.share, menu); - super.onPrepareOptionsMenu(menu); - return true; - } - @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); @@ -164,11 +216,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity startActivity(intent); } - @Override - public void onCreateConversation(long threadId, Recipient recipient, int distributionType) { - createConversation(threadId, recipient.getAddress(), distributionType); - } - private void handleResolvedMedia(Intent intent, boolean animate) { long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1); int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1); @@ -186,10 +233,10 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity boolean hasResolvedDestination = threadId != -1 && address != null && distributionType != -1; if (!hasResolvedDestination && animate) { - ViewUtil.fadeIn(fragmentContainer, 300); + ViewUtil.fadeIn(contactsFragment.getView(), 300); ViewUtil.fadeOut(progressWheel, 300); } else if (!hasResolvedDestination) { - fragmentContainer.setVisibility(View.VISIBLE); + contactsFragment.getView().setVisibility(View.VISIBLE); progressWheel.setVisibility(View.GONE); } else { createConversation(threadId, address, distributionType); @@ -223,10 +270,28 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity return MediaUtil.getCorrectedMimeType(getIntent().getType()); } + @Override + public void onContactSelected(String number) { + Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true); + long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient); + createConversation(existingThread, recipient.getAddress(), ThreadDatabase.DistributionTypes.DEFAULT); + } + + @Override + public void onContactDeselected(String number) { + + } + + @Override + public void onRefresh() { + + } + + @SuppressLint("StaticFieldLeak") private class ResolveMediaTask extends AsyncTask { private final Context context; - public ResolveMediaTask(Context context) { + ResolveMediaTask(Context context) { this.context = context; } diff --git a/src/org/thoughtcrime/securesms/ShareFragment.java b/src/org/thoughtcrime/securesms/ShareFragment.java deleted file mode 100644 index 44ac290d7..000000000 --- a/src/org/thoughtcrime/securesms/ShareFragment.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2014 Open Whisper Systems - * - * 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 . - */ -package org.thoughtcrime.securesms; - -import android.app.Activity; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v4.app.ListFragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; - -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.database.loaders.ConversationListLoader; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.recipients.Recipient; - -/** - * A fragment to select and share to open conversations - * - * @author Jake McGinty - */ -public class ShareFragment extends ListFragment implements LoaderManager.LoaderCallbacks { - - private ConversationSelectedListener listener; - private MasterSecret masterSecret; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - masterSecret = getArguments().getParcelable("master_secret"); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { - return inflater.inflate(R.layout.share_fragment, container, false); - } - - @Override - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - - initializeListAdapter(); - getLoaderManager().initLoader(0, null, this); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.listener = (ConversationSelectedListener) activity; - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - if (v instanceof ShareListItem) { - ShareListItem headerView = (ShareListItem) v; - - handleCreateConversation(headerView.getThreadId(), headerView.getRecipient(), - headerView.getDistributionType()); - } - } - - private void initializeListAdapter() { - this.setListAdapter(new ShareListAdapter(getActivity(), masterSecret, GlideApp.with(this), null)); - getListView().setRecyclerListener((ShareListAdapter) getListAdapter()); - getLoaderManager().restartLoader(0, null, this); - } - - private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) { - listener.onCreateConversation(threadId, recipient, distributionType); - } - - @Override - public Loader onCreateLoader(int arg0, Bundle arg1) { - return new ConversationListLoader(getActivity(), null, false); - } - - @Override - public void onLoadFinished(Loader arg0, Cursor cursor) { - ((CursorAdapter)getListAdapter()).changeCursor(cursor); - } - - @Override - public void onLoaderReset(Loader arg0) { - ((CursorAdapter)getListAdapter()).changeCursor(null); - } - - public interface ConversationSelectedListener { - public void onCreateConversation(long threadId, Recipient recipient, int distributionType); - } -} diff --git a/src/org/thoughtcrime/securesms/ShareListAdapter.java b/src/org/thoughtcrime/securesms/ShareListAdapter.java deleted file mode 100644 index a43fffae7..000000000 --- a/src/org/thoughtcrime/securesms/ShareListAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2014 Open Whisper Systems - * - * 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 . - */ -package org.thoughtcrime.securesms; - -import android.content.Context; -import android.database.Cursor; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; - -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.database.model.ThreadRecord; -import org.thoughtcrime.securesms.mms.GlideRequests; - -/** - * A CursorAdapter for building a list of open conversations - * - * @author Jake McGinty - */ -class ShareListAdapter extends CursorAdapter implements AbsListView.RecyclerListener { - - private final ThreadDatabase threadDatabase; - private final GlideRequests glideRequests; - private final MasterCipher masterCipher; - private final LayoutInflater inflater; - - ShareListAdapter(@NonNull Context context, @Nullable MasterSecret masterSecret, - @NonNull GlideRequests glideRequests, @Nullable Cursor cursor) - { - super(context, cursor, 0); - - if (masterSecret != null) this.masterCipher = new MasterCipher(masterSecret); - else this.masterCipher = null; - - this.glideRequests = glideRequests; - this.threadDatabase = DatabaseFactory.getThreadDatabase(context); - this.inflater = LayoutInflater.from(context); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return inflater.inflate(R.layout.share_list_item_view, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - if (masterCipher != null) { - ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor, masterCipher); - ThreadRecord record = reader.getCurrent(); - - ((ShareListItem)view).set(glideRequests, record); - } - } - - @Override - public void onMovedToScrapHeap(View view) { - ((ShareListItem)view).unbind(); - } -} diff --git a/src/org/thoughtcrime/securesms/ShareListItem.java b/src/org/thoughtcrime/securesms/ShareListItem.java deleted file mode 100644 index b5e9f46de..000000000 --- a/src/org/thoughtcrime/securesms/ShareListItem.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2014 Open Whisper Systems - * - * 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 . - */ -package org.thoughtcrime.securesms; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.NonNull; -import android.util.AttributeSet; -import android.widget.RelativeLayout; - -import org.thoughtcrime.securesms.components.AvatarImageView; -import org.thoughtcrime.securesms.components.FromTextView; -import org.thoughtcrime.securesms.database.model.ThreadRecord; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientModifiedListener; -import org.thoughtcrime.securesms.util.Util; - -/** - * A simple view to show the recipients of an open conversation - * - * @author Jake McGinty - */ -public class ShareListItem extends RelativeLayout - implements RecipientModifiedListener -{ - private final static String TAG = ShareListItem.class.getSimpleName(); - - private Context context; - private GlideRequests glideRequests; - private Recipient recipient; - private long threadId; - private FromTextView fromView; - - private AvatarImageView contactPhotoImage; - - private int distributionType; - - public ShareListItem(Context context) { - super(context); - this.context = context; - } - - public ShareListItem(Context context, AttributeSet attrs) { - super(context, attrs); - this.context = context; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - this.fromView = findViewById(R.id.from); - this.contactPhotoImage = findViewById(R.id.contact_photo_image); - } - - public void set(@NonNull GlideRequests glideRequests, @NonNull ThreadRecord thread) { - this.glideRequests = glideRequests; - this.recipient = thread.getRecipient(); - this.threadId = thread.getThreadId(); - this.distributionType = thread.getDistributionType(); - - this.recipient.addListener(this); - this.fromView.setText(recipient); - - setBackground(); - this.contactPhotoImage.setAvatar(glideRequests, this.recipient, false); - } - - public void unbind() { - if (this.recipient != null) this.recipient.removeListener(this); - } - - private void setBackground() { - int[] attributes = new int[]{R.attr.conversation_list_item_background}; - TypedArray drawables = context.obtainStyledAttributes(attributes); - - setBackgroundDrawable(drawables.getDrawable(0)); - - drawables.recycle(); - } - - public Recipient getRecipient() { - return recipient; - } - - public long getThreadId() { - return threadId; - } - - public int getDistributionType() { - return distributionType; - } - - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> { - fromView.setText(recipient); - contactPhotoImage.setAvatar(glideRequests, recipient, false); - }); - } -}