diff --git a/res/anim/slide_from_left.xml b/res/anim/slide_from_left.xml
new file mode 100644
index 0000000000..8ad83d09ed
--- /dev/null
+++ b/res/anim/slide_from_left.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/anim/slide_to_left.xml b/res/anim/slide_to_left.xml
new file mode 100644
index 0000000000..698746867a
--- /dev/null
+++ b/res/anim/slide_to_left.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_arrow_right.png b/res/drawable-hdpi/ic_arrow_right.png
new file mode 100644
index 0000000000..bbc68bf0f6
Binary files /dev/null and b/res/drawable-hdpi/ic_arrow_right.png differ
diff --git a/res/drawable-hdpi/ic_select_off.png b/res/drawable-hdpi/ic_select_off.png
new file mode 100644
index 0000000000..644c55711f
Binary files /dev/null and b/res/drawable-hdpi/ic_select_off.png differ
diff --git a/res/drawable-hdpi/ic_select_on.png b/res/drawable-hdpi/ic_select_on.png
new file mode 100644
index 0000000000..e50fcbdea7
Binary files /dev/null and b/res/drawable-hdpi/ic_select_on.png differ
diff --git a/res/drawable-mdpi/ic_arrow_right.png b/res/drawable-mdpi/ic_arrow_right.png
new file mode 100644
index 0000000000..0e3af28d8e
Binary files /dev/null and b/res/drawable-mdpi/ic_arrow_right.png differ
diff --git a/res/drawable-mdpi/ic_select_off.png b/res/drawable-mdpi/ic_select_off.png
new file mode 100644
index 0000000000..fcb3c6a4ed
Binary files /dev/null and b/res/drawable-mdpi/ic_select_off.png differ
diff --git a/res/drawable-mdpi/ic_select_on.png b/res/drawable-mdpi/ic_select_on.png
new file mode 100644
index 0000000000..464f1fcf67
Binary files /dev/null and b/res/drawable-mdpi/ic_select_on.png differ
diff --git a/res/drawable-v21/media_count_button_background.xml b/res/drawable-v21/media_count_button_background.xml
new file mode 100644
index 0000000000..a5fdc45a06
--- /dev/null
+++ b/res/drawable-v21/media_count_button_background.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable-xhdpi/ic_arrow_right.png b/res/drawable-xhdpi/ic_arrow_right.png
new file mode 100644
index 0000000000..510690366d
Binary files /dev/null and b/res/drawable-xhdpi/ic_arrow_right.png differ
diff --git a/res/drawable-xhdpi/ic_select_off.png b/res/drawable-xhdpi/ic_select_off.png
new file mode 100644
index 0000000000..2368c9e3d6
Binary files /dev/null and b/res/drawable-xhdpi/ic_select_off.png differ
diff --git a/res/drawable-xhdpi/ic_select_on.png b/res/drawable-xhdpi/ic_select_on.png
new file mode 100644
index 0000000000..9a83575aeb
Binary files /dev/null and b/res/drawable-xhdpi/ic_select_on.png differ
diff --git a/res/drawable-xxhdpi/ic_arrow_right.png b/res/drawable-xxhdpi/ic_arrow_right.png
new file mode 100644
index 0000000000..daa544853a
Binary files /dev/null and b/res/drawable-xxhdpi/ic_arrow_right.png differ
diff --git a/res/drawable-xxhdpi/ic_select_off.png b/res/drawable-xxhdpi/ic_select_off.png
new file mode 100644
index 0000000000..a4bf4e53d9
Binary files /dev/null and b/res/drawable-xxhdpi/ic_select_off.png differ
diff --git a/res/drawable-xxhdpi/ic_select_on.png b/res/drawable-xxhdpi/ic_select_on.png
new file mode 100644
index 0000000000..51326566f7
Binary files /dev/null and b/res/drawable-xxhdpi/ic_select_on.png differ
diff --git a/res/drawable-xxxhdpi/ic_arrow_right.png b/res/drawable-xxxhdpi/ic_arrow_right.png
new file mode 100644
index 0000000000..f7ce1009b8
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_arrow_right.png differ
diff --git a/res/drawable-xxxhdpi/ic_select_off.png b/res/drawable-xxxhdpi/ic_select_off.png
new file mode 100644
index 0000000000..da08bfe9a9
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_select_off.png differ
diff --git a/res/drawable-xxxhdpi/ic_select_on.png b/res/drawable-xxxhdpi/ic_select_on.png
new file mode 100644
index 0000000000..177ce25072
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_select_on.png differ
diff --git a/res/drawable/media_count_button_background.xml b/res/drawable/media_count_button_background.xml
new file mode 100644
index 0000000000..be4d965cfc
--- /dev/null
+++ b/res/drawable/media_count_button_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/res/drawable/media_count_number_background.xml b/res/drawable/media_count_number_background.xml
new file mode 100644
index 0000000000..1ffe671559
--- /dev/null
+++ b/res/drawable/media_count_number_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/pill.xml b/res/drawable/pill.xml
new file mode 100644
index 0000000000..be4d965cfc
--- /dev/null
+++ b/res/drawable/pill.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/res/layout/mediapicker_activity.xml b/res/layout/mediapicker_activity.xml
deleted file mode 100644
index b7068b063a..0000000000
--- a/res/layout/mediapicker_activity.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/layout/mediapicker_media_item.xml b/res/layout/mediapicker_media_item.xml
index bc9c28bd19..e5aa0150a8 100644
--- a/res/layout/mediapicker_media_item.xml
+++ b/res/layout/mediapicker_media_item.xml
@@ -6,14 +6,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="2dp"
- android:layout_marginBottom="2dp">
+ android:layout_marginBottom="2dp"
+ android:animateLayoutChanges="true">
+ android:scaleType="centerCrop"
+ tools:src="@drawable/empty_inbox_1"/>
+ tools:visibility="gone">
-
-
+ android:background="@color/transparent_black_90" />
-
+
-
+
\ No newline at end of file
diff --git a/res/layout/mediasend_activity.xml b/res/layout/mediasend_activity.xml
new file mode 100644
index 0000000000..f279426812
--- /dev/null
+++ b/res/layout/mediasend_activity.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java
index 9215cddf5a..08c47a7399 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaPickerFolderFragment.java
@@ -15,6 +15,7 @@ import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.GlideApp;
@@ -88,6 +89,14 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
initToolbar(view.findViewById(R.id.mediapicker_toolbar));
}
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemAdapter.java b/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemAdapter.java
index 651c8c4874..c7217b8976 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemAdapter.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemAdapter.java
@@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.util.StableIdGenerator;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@@ -38,11 +39,7 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter();
this.maxSelection = maxSelection;
this.stableIdGenerator = new StableIdGenerator<>();
- this.selected = new TreeSet<>((m1, m2) -> {
- if (m1.equals(m2)) return 0;
- else if (Long.compare(m2.getDate(), m1.getDate()) == 0) return m2.getUri().compareTo(m1.getUri());
- else return Long.compare(m2.getDate(), m1.getDate());
- });
+ this.selected = new LinkedHashSet<>();
setHasStableIds(true);
}
@@ -97,13 +94,17 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter selected, int maxSelection, @NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
@@ -113,10 +114,13 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter eventListener.onMediaChosen(media));
+ selectOn.setVisibility(View.GONE);
+ selectOff.setVisibility(View.GONE);
+ selectOverlay.setVisibility(View.GONE);
+
if (maxSelection > 1) {
itemView.setOnLongClickListener(v -> {
selected.add(media);
@@ -125,11 +129,17 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter {
selected.remove(media);
eventListener.onMediaSelectionChanged(new ArrayList<>(selected));
});
} else {
+ selectOff.setVisibility(View.VISIBLE);
+ selectOn.setVisibility(View.GONE);
+ selectOverlay.setVisibility(View.GONE);
itemView.setOnClickListener(v -> {
if (selected.size() < maxSelection) {
selected.add(media);
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java
index e76bc62a03..5927e50a48 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaPickerItemFragment.java
@@ -50,8 +50,6 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
private MediaPickerItemAdapter adapter;
private Controller controller;
private GridLayoutManager layoutManager;
- private ActionMode actionMode;
- private ActionMode.Callback actionModeCallback;
public static MediaPickerItemFragment newInstance(@NonNull String bucketId, @NonNull String folderTitle, int maxSelection) {
Bundle args = new Bundle();
@@ -70,11 +68,10 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
- bucketId = getArguments().getString(KEY_BUCKET_ID);
- folderTitle = getArguments().getString(KEY_FOLDER_TITLE);
- maxSelection = getArguments().getInt(KEY_MAX_SELECTION);
- viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
- actionModeCallback = new ActionModeCallback();
+ bucketId = getArguments().getString(KEY_BUCKET_ID);
+ folderTitle = getArguments().getString(KEY_FOLDER_TITLE);
+ maxSelection = getArguments().getInt(KEY_MAX_SELECTION);
+ viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
}
@Override
@@ -114,6 +111,8 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
}
viewModel.getMediaInBucket(requireContext(), bucketId).observe(this, adapter::setMedia);
+
+ initMediaObserver(viewModel);
}
@Override
@@ -125,16 +124,19 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- inflater.inflate(R.menu.mediapicker_default, menu);
+ public void onPrepareOptionsMenu(Menu menu) {
+ requireActivity().getMenuInflater().inflate(R.menu.mediapicker_default, menu);
+
+ MenuItem beginSelectionButton = menu.findItem(R.id.mediapicker_menu_add);
+
+ beginSelectionButton.setVisible(!viewModel.getCountButtonState().getValue().getVisibility());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.mediapicker_menu_add) {
adapter.setForcedMultiSelect(true);
- actionMode = ((AppCompatActivity) requireActivity()).startSupportActionMode(actionModeCallback);
- actionMode.setTitle(getResources().getString(R.string.MediaPickerItemFragment_tap_to_select));
+ viewModel.onMultiSelectStarted();
return true;
}
return false;
@@ -148,23 +150,13 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
@Override
public void onMediaChosen(@NonNull Media media) {
- controller.onMediaSelected(bucketId, Collections.singleton(media));
viewModel.onSelectedMediaChanged(requireContext(), Collections.singletonList(media));
+ controller.onMediaSelected(bucketId);
}
@Override
public void onMediaSelectionChanged(@NonNull List selected) {
adapter.notifyDataSetChanged();
-
- if (actionMode == null && !selected.isEmpty()) {
- actionMode = ((AppCompatActivity) requireActivity()).startSupportActionMode(actionModeCallback);
- actionMode.setTitle(String.valueOf(selected.size()));
- } else if (actionMode != null && selected.isEmpty()) {
- actionMode.finish();
- } else if (actionMode != null) {
- actionMode.setTitle(String.valueOf(selected.size()));
- }
-
viewModel.onSelectedMediaChanged(requireContext(), selected);
}
@@ -181,6 +173,12 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
}
+ private void initMediaObserver(@NonNull MediaSendViewModel viewModel) {
+ viewModel.getCountButtonState().observe(this, media -> {
+ requireActivity().invalidateOptionsMenu();
+ });
+ }
+
private void onScreenWidthChanged(int newWidth) {
if (layoutManager != null) {
layoutManager.setSpanCount(newWidth / getResources().getDimensionPixelSize(R.dimen.media_picker_item_width));
@@ -193,55 +191,7 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
return size.x;
}
- private class ActionModeCallback implements ActionMode.Callback {
-
- private int statusBarColor;
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.mediapicker_multiselect, menu);
-
- if (Build.VERSION.SDK_INT >= 21) {
- Window window = requireActivity().getWindow();
- statusBarColor = window.getStatusBarColor();
- window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar));
- }
-
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) {
- if (menuItem.getItemId() == R.id.mediapicker_menu_confirm) {
- List selected = new ArrayList<>(adapter.getSelected());
- actionMode.finish();
- viewModel.onSelectedMediaChanged(requireContext(), selected);
- controller.onMediaSelected(bucketId, selected);
- return true;
- }
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- actionMode = null;
- adapter.setSelected(Collections.emptySet());
- viewModel.onSelectedMediaChanged(requireContext(), Collections.emptyList());
-
- if (Build.VERSION.SDK_INT >= 21) {
- requireActivity().getWindow().setStatusBarColor(statusBarColor);
- }
- }
- }
-
-
public interface Controller {
- void onMediaSelected(@NonNull String bucketId, @NonNull Collection media);
+ void onMediaSelected(@NonNull String bucketId);
}
}
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java
index 3441cccf5b..115aabbc23 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendActivity.java
@@ -6,11 +6,18 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.database.Address;
+import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
@@ -23,6 +30,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
/**
* Encompasses the entire flow of sending media, starting from the selection process to the actual
@@ -56,10 +64,12 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private Recipient recipient;
- private String body;
private TransportOption transport;
private MediaSendViewModel viewModel;
+ private View countButton;
+ private TextView countButtonText;
+
/**
* Get an intent to launch the media send flow starting with the picker.
*/
@@ -94,28 +104,42 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
- setContentView(R.layout.mediapicker_activity);
+ setContentView(R.layout.mediasend_activity);
setResult(RESULT_CANCELED);
if (savedInstanceState != null) {
return;
}
+ countButton = findViewById(R.id.mediasend_count_button);
+ countButtonText = findViewById(R.id.mediasend_count_button_text);
+
viewModel = ViewModelProviders.of(this, new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
recipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true);
- body = getIntent().getStringExtra(KEY_BODY);
transport = getIntent().getParcelableExtra(KEY_TRANSPORT);
viewModel.setMediaConstraints(transport.isSms() ? MediaConstraints.getMmsMediaConstraints(transport.getSimSubscriptionId().or(-1))
: MediaConstraints.getPushMediaConstraints());
+ viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY));
+
List media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
if (!Util.isEmpty(media)) {
- navigateToMediaSend(media, body, transport);
+ viewModel.onSelectedMediaChanged(this, media);
+
+ Fragment fragment = MediaSendFragment.newInstance(transport, dynamicLanguage.getCurrentLocale());
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND)
+ .commit();
} else {
- navigateToFolderPicker(recipient);
+ MediaPickerFolderFragment fragment = MediaPickerFolderFragment.newInstance(recipient);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.mediasend_fragment_container, fragment, TAG_FOLDER_PICKER)
+ .commit();
}
+
+ initializeCountButtonObserver(transport, dynamicLanguage.getCurrentLocale());
}
@Override
@@ -137,41 +161,34 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
public void onFolderSelected(@NonNull MediaFolder folder) {
viewModel.onFolderSelected(folder.getBucketId());
- MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(),
- folder.getTitle(),
- transport.isSms() ? MAX_SMS : MAX_PUSH);
-
+ MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(), folder.getTitle(), transport.isSms() ? MAX_SMS :MAX_PUSH);
getSupportFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
- .replace(R.id.mediapicker_fragment_container, fragment, TAG_ITEM_PICKER)
+ .setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
+ .replace(R.id.mediasend_fragment_container, fragment, TAG_ITEM_PICKER)
.addToBackStack(null)
.commit();
}
@Override
- public void onMediaSelected(@NonNull String bucketId, @NonNull Collection media) {
- MediaSendFragment fragment = MediaSendFragment.newInstance(body, transport, dynamicLanguage.getCurrentLocale());
- getSupportFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
- .replace(R.id.mediapicker_fragment_container, fragment, TAG_SEND)
- .addToBackStack(null)
- .commit();
+ public void onMediaSelected(@NonNull String bucketId) {
+ navigateToMediaSend(transport, dynamicLanguage.getCurrentLocale());
}
@Override
public void onAddMediaClicked(@NonNull String bucketId) {
+ // TODO: Get actual folder title somehow
MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient);
- MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId,
- "",
- transport.isSms() ? MAX_SMS : MAX_PUSH);
+ MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId, "", transport.isSms() ? MAX_SMS : MAX_PUSH);
getSupportFragmentManager().beginTransaction()
- .replace(R.id.mediapicker_fragment_container, folderFragment, TAG_FOLDER_PICKER)
+ .setCustomAnimations(R.anim.stationary, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
+ .replace(R.id.mediasend_fragment_container, folderFragment, TAG_FOLDER_PICKER)
.addToBackStack(null)
.commit();
getSupportFragmentManager().beginTransaction()
- .replace(R.id.mediapicker_fragment_container, itemFragment, TAG_ITEM_PICKER)
+ .setCustomAnimations(R.anim.slide_from_right, R.anim.stationary, R.anim.slide_from_left, R.anim.slide_to_right)
+ .replace(R.id.mediasend_fragment_container, itemFragment, TAG_ITEM_PICKER)
.addToBackStack(null)
.commit();
}
@@ -214,20 +231,29 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
}
}
- private void navigateToMediaSend(List media, String body, TransportOption transport) {
- viewModel.setInitialSelectedMedia(this, media);
+ private void initializeCountButtonObserver(@NonNull TransportOption transport, @NonNull Locale locale) {
+ viewModel.getCountButtonState().observe(this, buttonState -> {
+ if (buttonState == null) return;
- MediaSendFragment sendFragment = MediaSendFragment.newInstance(body, transport, dynamicLanguage.getCurrentLocale());
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.mediapicker_fragment_container, sendFragment, TAG_SEND)
- .commit();
+ countButton.setVisibility(buttonState.getVisibility() ? View.VISIBLE : View.GONE);
+ countButton.setOnClickListener(v -> navigateToMediaSend(transport, locale));
+ countButtonText.setText(String.valueOf(buttonState.getCount()));
+ });
}
- private void navigateToFolderPicker(@NonNull Recipient recipient) {
- MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient);
+ private void navigateToMediaSend(@NonNull TransportOption transport, @NonNull Locale locale) {
+ MediaSendFragment fragment = MediaSendFragment.newInstance(transport, locale);
+ String backstackTag = null;
+
+ if (getSupportFragmentManager().findFragmentByTag(TAG_SEND) != null) {
+ getSupportFragmentManager().popBackStack(TAG_SEND, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ backstackTag = TAG_SEND;
+ }
getSupportFragmentManager().beginTransaction()
- .replace(R.id.mediapicker_fragment_container, folderFragment, TAG_FOLDER_PICKER)
+ .setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
+ .replace(R.id.mediasend_fragment_container, fragment, TAG_SEND)
+ .addToBackStack(backstackTag)
.commit();
}
}
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java
index c96ad9f069..dc81b83c1b 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java
@@ -72,7 +72,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
private static final String TAG = MediaSendFragment.class.getSimpleName();
- private static final String KEY_BODY = "body";
private static final String KEY_TRANSPORT = "transport";
private static final String KEY_LOCALE = "locale";
@@ -99,9 +98,8 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
private final Rect visibleBounds = new Rect();
- public static MediaSendFragment newInstance(@NonNull String body, @NonNull TransportOption transport, @NonNull Locale locale) {
+ public static MediaSendFragment newInstance(@NonNull TransportOption transport, @NonNull Locale locale) {
Bundle args = new Bundle();
- args.putString(KEY_BODY, body);
args.putParcelable(KEY_TRANSPORT, transport);
args.putSerializable(KEY_LOCALE, locale);
@@ -134,9 +132,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
locale = (Locale) getArguments().getSerializable(KEY_LOCALE);
initViewModel();
-
- requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
}
@Override
@@ -181,7 +176,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
captionText.clearFocus();
composeText.requestFocus();
- fragmentPagerAdapter = new MediaSendFragmentPagerAdapter(requireActivity().getSupportFragmentManager(), locale);
+ fragmentPagerAdapter = new MediaSendFragmentPagerAdapter(getChildFragmentManager(), locale);
fragmentPager.setAdapter(fragmentPagerAdapter);
FragmentPageChangeListener pageChangeListener = new FragmentPageChangeListener();
@@ -208,7 +203,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
sendButton.setTransport(transportOption);
sendButton.disableTransport(transportOption.getType() == TransportOption.Type.SMS ? TransportOption.Type.TEXTSECURE : TransportOption.Type.SMS);
- composeText.append(getArguments().getString(KEY_BODY));
+ composeText.append(viewModel.getBody());
if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
@@ -221,13 +216,25 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
@Override
public void onStart() {
super.onStart();
+
fragmentPagerAdapter.restoreState(viewModel.getDrawState());
+ viewModel.onImageEditorStarted();
+
+ requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ }
+
+ @Override
+ public void onHiddenChanged(boolean hidden) {
+ super.onHiddenChanged(hidden);
}
@Override
public void onStop() {
super.onStop();
+ fragmentPagerAdapter.saveAllState();
viewModel.saveDrawState(fragmentPagerAdapter.getSavedState());
+ viewModel.onImageEditorEnded();
}
@Override
@@ -328,11 +335,13 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
});
viewModel.getBucketId().observe(this, bucketId -> {
- if (bucketId == null || !bucketId.isPresent() || sendButton.getSelectedTransport().isSms()) {
+ if (bucketId == null) return;
+
+ if (sendButton.getSelectedTransport().isSms()) {
addButton.setVisibility(View.GONE);
} else {
addButton.setVisibility(View.VISIBLE);
- addButton.setOnClickListener(v -> controller.onAddMediaClicked(bucketId.get()));
+ addButton.setOnClickListener(v -> controller.onAddMediaClicked(bucketId));
}
});
@@ -505,6 +514,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
@Override
public void afterTextChanged(Editable s) {
presentCharactersRemaining();
+ viewModel.onBodyChanged(s);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java
index d85eba9f1f..2283504fb8 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendFragmentPagerAdapter.java
@@ -9,6 +9,7 @@ import android.support.v4.app.FragmentStatePagerAdapter;
import android.view.View;
import android.view.ViewGroup;
+import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -106,6 +107,15 @@ class MediaSendFragmentPagerAdapter extends FragmentStatePagerAdapter {
return new HashMap<>(savedState);
}
+ void saveAllState() {
+ for (MediaSendPageFragment fragment : fragments.values()) {
+ Object state = fragment.saveState();
+ if (state != null) {
+ savedState.put(fragment.getUri(), state);
+ }
+ }
+ }
+
void restoreState(@NonNull Map state) {
savedState.clear();
savedState.putAll(state);
diff --git a/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java b/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java
index 7bf95e6337..9212e1cdb6 100644
--- a/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java
+++ b/src/org/thoughtcrime/securesms/mediasend/MediaSendViewModel.java
@@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.Util;
-import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Collections;
import java.util.HashMap;
@@ -31,31 +30,37 @@ class MediaSendViewModel extends ViewModel {
private final MutableLiveData> selectedMedia;
private final MutableLiveData> bucketMedia;
private final MutableLiveData position;
- private final MutableLiveData> bucketId;
+ private final MutableLiveData bucketId;
private final MutableLiveData> folders;
+ private final MutableLiveData countButtonState;
private final SingleLiveEvent error;
private final Map savedDrawState;
- private MediaConstraints mediaConstraints;
+ private MediaConstraints mediaConstraints;
+ private CharSequence body;
+ private CountButtonState.Visibility countButtonVisibility;
private MediaSendViewModel(@NonNull MediaRepository repository) {
- this.repository = repository;
- this.selectedMedia = new MutableLiveData<>();
- this.bucketMedia = new MutableLiveData<>();
- this.position = new MutableLiveData<>();
- this.bucketId = new MutableLiveData<>();
- this.folders = new MutableLiveData<>();
- this.error = new SingleLiveEvent<>();
- this.savedDrawState = new HashMap<>();
+ this.repository = repository;
+ this.selectedMedia = new MutableLiveData<>();
+ this.bucketMedia = new MutableLiveData<>();
+ this.position = new MutableLiveData<>();
+ this.bucketId = new MutableLiveData<>();
+ this.folders = new MutableLiveData<>();
+ this.countButtonState = new MutableLiveData<>();
+ this.error = new SingleLiveEvent<>();
+ this.savedDrawState = new HashMap<>();
+ this.countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
position.setValue(-1);
+ countButtonState.setValue(new CountButtonState(0, CountButtonState.Visibility.CONDITIONAL));
}
void setMediaConstraints(@NonNull MediaConstraints mediaConstraints) {
this.mediaConstraints = mediaConstraints;
}
- void setInitialSelectedMedia(@NonNull Context context, @NonNull List newMedia) {
+ void onSelectedMediaChanged(@NonNull Context context, @NonNull List newMedia) {
repository.getPopulatedMedia(context, newMedia, populatedMedia -> {
List filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
@@ -63,26 +68,48 @@ class MediaSendViewModel extends ViewModel {
error.postValue(Error.ITEM_TOO_LARGE);
}
- boolean allBucketsPopulated = Stream.of(filteredMedia).reduce(true, (populated, m) -> populated && m.getBucketId().isPresent());
+ if (filteredMedia.size() > 0) {
+ String computedId = Stream.of(filteredMedia)
+ .skip(1)
+ .reduce(filteredMedia.get(0).getBucketId().orNull(), (id, m) -> {
+ if (Util.equals(id, m.getBucketId().orNull())) {
+ return id;
+ } else {
+ return Media.ALL_MEDIA_BUCKET_ID;
+ }
+ });
+ bucketId.postValue(computedId);
+ } else {
+ bucketId.postValue(Media.ALL_MEDIA_BUCKET_ID);
+ countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
+ }
selectedMedia.postValue(filteredMedia);
- bucketId.postValue(allBucketsPopulated ? computeBucketId(filteredMedia) : Optional.absent());
+ countButtonState.postValue(new CountButtonState(filteredMedia.size(), countButtonVisibility));
});
}
- void onSelectedMediaChanged(@NonNull Context context, @NonNull List newMedia) {
- List filteredMedia = getFilteredMedia(context, newMedia, mediaConstraints);
+ void onMultiSelectStarted() {
+ countButtonVisibility = CountButtonState.Visibility.FORCED_ON;
+ countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
+ }
- if (filteredMedia.size() != newMedia.size()) {
- error.setValue(Error.ITEM_TOO_LARGE);
- }
+ void onImageEditorStarted() {
+ countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
+ countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
+ }
+
+ void onImageEditorEnded() {
+ countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
+ countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
+ }
- selectedMedia.setValue(filteredMedia);
- position.setValue(filteredMedia.isEmpty() ? -1 : 0);
+ void onBodyChanged(@NonNull CharSequence body) {
+ this.body = body;
}
void onFolderSelected(@NonNull String bucketId) {
- this.bucketId.setValue(Optional.of(bucketId));
+ this.bucketId.setValue(bucketId);
bucketMedia.setValue(Collections.emptyList());
}
@@ -91,7 +118,7 @@ class MediaSendViewModel extends ViewModel {
}
void onMediaItemRemoved(int position) {
- selectedMedia.getValue().remove(position);
+ getSelectedMediaOrDefault().remove(position);
selectedMedia.setValue(selectedMedia.getValue());
}
@@ -110,11 +137,11 @@ class MediaSendViewModel extends ViewModel {
return savedDrawState;
}
- LiveData> getSelectedMedia() {
+ @NonNull LiveData> getSelectedMedia() {
return selectedMedia;
}
- LiveData> getMediaInBucket(@NonNull Context context, @NonNull String bucketId) {
+ @NonNull LiveData> getMediaInBucket(@NonNull Context context, @NonNull String bucketId) {
repository.getMediaInBucket(context, bucketId, bucketMedia::postValue);
return bucketMedia;
}
@@ -124,11 +151,19 @@ class MediaSendViewModel extends ViewModel {
return folders;
}
+ @NonNull LiveData getCountButtonState() {
+ return countButtonState;
+ }
+
+ CharSequence getBody() {
+ return body;
+ }
+
LiveData getPosition() {
return position;
}
- LiveData> getBucketId() {
+ LiveData getBucketId() {
return bucketId;
}
@@ -136,17 +171,9 @@ class MediaSendViewModel extends ViewModel {
return error;
}
- private Optional computeBucketId(@NonNull List media) {
- if (media.isEmpty() || !media.get(0).getBucketId().isPresent()) return Optional.absent();
-
- String candidate = media.get(0).getBucketId().get();
- for (int i = 1; i < media.size(); i++) {
- if (!Util.equals(candidate, media.get(i).getBucketId().orNull())) {
- return Optional.of(Media.ALL_MEDIA_BUCKET_ID);
- }
- }
-
- return Optional.of(candidate);
+ private @NonNull List getSelectedMediaOrDefault() {
+ return selectedMedia.getValue() == null ? Collections.emptyList()
+ : selectedMedia.getValue();
}
private @NonNull List getFilteredMedia(@NonNull Context context, @NonNull List media, @NonNull MediaConstraints mediaConstraints) {
@@ -165,6 +192,33 @@ class MediaSendViewModel extends ViewModel {
ITEM_TOO_LARGE
}
+ static class CountButtonState {
+ private final int count;
+ private final Visibility visibility;
+
+ private CountButtonState(int count, @NonNull Visibility visibility) {
+ this.count = count;
+ this.visibility = visibility;
+ }
+
+ int getCount() {
+ return count;
+ }
+
+ boolean getVisibility() {
+ switch (visibility) {
+ case FORCED_ON: return true;
+ case FORCED_OFF: return false;
+ case CONDITIONAL: return count > 0;
+ default: return false;
+ }
+ }
+
+ enum Visibility {
+ CONDITIONAL, FORCED_ON, FORCED_OFF
+ }
+ }
+
static class Factory extends ViewModelProvider.NewInstanceFactory {
private final MediaRepository repository;
diff --git a/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java b/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java
index 9c8dd215df..a5b08f8eed 100644
--- a/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java
+++ b/src/org/thoughtcrime/securesms/scribbles/ScribbleFragment.java
@@ -147,6 +147,10 @@ public class ScribbleFragment extends Fragment implements ScribbleHud.EventListe
public void restoreState(@NonNull Object state) {
if (state instanceof ScribbleView.SavedState) {
savedState = (ScribbleView.SavedState) state;
+
+ if (scribbleView != null) {
+ scribbleView.restoreState(savedState);
+ }
} else {
Log.w(TAG, "Received a bad saved state. Received class: " + state.getClass().getName());
}
diff --git a/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java b/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java
index 2986dc1f8b..a7453e24de 100644
--- a/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java
+++ b/src/org/thoughtcrime/securesms/scribbles/widget/ScribbleView.java
@@ -33,6 +33,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
@@ -87,6 +88,7 @@ public class ScribbleView extends FrameLayout {
glideRequests.load(new DecryptableUri(uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
+ .transition(DrawableTransitionOptions.withCrossFade())
.fitCenter()
.into(imageView);
}