Persistent media in multi-send.
9
res/anim/slide_from_left.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:interpolator="@android:anim/decelerate_interpolator">
|
||||||
|
<translate
|
||||||
|
android:duration="150"
|
||||||
|
android:fromXDelta="-100%"
|
||||||
|
android:toXDelta="0%" />
|
||||||
|
</set>
|
9
res/anim/slide_to_left.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:interpolator="@android:anim/decelerate_interpolator">
|
||||||
|
<translate
|
||||||
|
android:duration="150"
|
||||||
|
android:fromXDelta="0%"
|
||||||
|
android:toXDelta="-100%" />
|
||||||
|
</set>
|
BIN
res/drawable-hdpi/ic_arrow_right.png
Normal file
After Width: | Height: | Size: 260 B |
BIN
res/drawable-hdpi/ic_select_off.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
res/drawable-hdpi/ic_select_on.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
res/drawable-mdpi/ic_arrow_right.png
Normal file
After Width: | Height: | Size: 238 B |
BIN
res/drawable-mdpi/ic_select_off.png
Normal file
After Width: | Height: | Size: 923 B |
BIN
res/drawable-mdpi/ic_select_on.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
12
res/drawable-v21/media_count_button_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@color/transparent_white_40">
|
||||||
|
<item android:id="@+id/mask">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="1000dp" />
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:drawable="@drawable/pill" />
|
||||||
|
</ripple>
|
BIN
res/drawable-xhdpi/ic_arrow_right.png
Normal file
After Width: | Height: | Size: 344 B |
BIN
res/drawable-xhdpi/ic_select_off.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-xhdpi/ic_select_on.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/drawable-xxhdpi/ic_arrow_right.png
Normal file
After Width: | Height: | Size: 508 B |
BIN
res/drawable-xxhdpi/ic_select_off.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
res/drawable-xxhdpi/ic_select_on.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
res/drawable-xxxhdpi/ic_arrow_right.png
Normal file
After Width: | Height: | Size: 652 B |
BIN
res/drawable-xxxhdpi/ic_select_off.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
res/drawable-xxxhdpi/ic_select_on.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
5
res/drawable/media_count_button_background.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="1000dp" />
|
||||||
|
<solid android:color="@color/signal_primary" />
|
||||||
|
</shape>
|
5
res/drawable/media_count_number_background.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="1000dp" />
|
||||||
|
<solid android:color="@color/core_white" />
|
||||||
|
</shape>
|
5
res/drawable/pill.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="1000dp" />
|
||||||
|
<solid android:color="@color/signal_primary" />
|
||||||
|
</shape>
|
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/mediapicker_fragment_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -6,14 +6,16 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginRight="2dp"
|
android:layout_marginRight="2dp"
|
||||||
android:layout_marginBottom="2dp">
|
android:layout_marginBottom="2dp"
|
||||||
|
android:animateLayoutChanges="true">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.SquareImageView
|
<org.thoughtcrime.securesms.components.SquareImageView
|
||||||
android:id="@+id/mediapicker_image_item_thumbnail"
|
android:id="@+id/mediapicker_image_item_thumbnail"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="centerCrop"/>
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@drawable/empty_inbox_1"/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -28,7 +30,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:longClickable="false"
|
android:longClickable="false"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="gone">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="15dp"
|
android:layout_width="15dp"
|
||||||
@ -41,20 +43,29 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
<FrameLayout
|
android:id="@+id/mediapicker_select_overlay"
|
||||||
android:id="@+id/mediapicker_selected"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/transparent_black_90"
|
android:background="@color/transparent_black_90" />
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/mediapicker_select_on"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_height="wrap_content"
|
||||||
android:src="@drawable/ic_check_white_24dp" />
|
android:layout_gravity="top|right|end"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:src="@drawable/ic_select_on"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</FrameLayout>
|
<ImageView
|
||||||
|
android:id="@+id/mediapicker_select_off"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top|right|end"
|
||||||
|
android:padding="6dp"
|
||||||
|
android:src="@drawable/ic_select_off"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
54
res/layout/mediasend_activity.xml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/mediasend_fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mediasend_count_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:layout_marginRight="32dp"
|
||||||
|
android:layout_gravity="bottom|right|end"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/media_count_button_background"
|
||||||
|
android:elevation="4dp"
|
||||||
|
tools:parentTag="android.widget.LinearLayout">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mediasend_count_button_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="28dp"
|
||||||
|
android:paddingLeft="7dp"
|
||||||
|
android:paddingRight="7dp"
|
||||||
|
android:paddingTop="2dp"
|
||||||
|
android:paddingBottom="2dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@drawable/media_count_number_background"
|
||||||
|
android:textColor="@color/signal_primary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="3" />
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_marginLeft="2dp"
|
||||||
|
android:layout_marginStart="2dp"
|
||||||
|
android:src="@drawable/ic_arrow_right"
|
||||||
|
android:tint="@color/core_white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -15,6 +15,7 @@ import android.support.v7.widget.Toolbar;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
@ -88,6 +89,14 @@ public class MediaPickerFolderFragment extends Fragment implements MediaPickerFo
|
|||||||
initToolbar(view.findViewById(R.id.mediapicker_toolbar));
|
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
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
|
@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.util.StableIdGenerator;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
@ -38,11 +39,7 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter<MediaPickerItem
|
|||||||
this.media = new ArrayList<>();
|
this.media = new ArrayList<>();
|
||||||
this.maxSelection = maxSelection;
|
this.maxSelection = maxSelection;
|
||||||
this.stableIdGenerator = new StableIdGenerator<>();
|
this.stableIdGenerator = new StableIdGenerator<>();
|
||||||
this.selected = new TreeSet<>((m1, m2) -> {
|
this.selected = new LinkedHashSet<>();
|
||||||
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());
|
|
||||||
});
|
|
||||||
|
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
}
|
}
|
||||||
@ -97,13 +94,17 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter<MediaPickerItem
|
|||||||
|
|
||||||
private final ImageView thumbnail;
|
private final ImageView thumbnail;
|
||||||
private final View playOverlay;
|
private final View playOverlay;
|
||||||
private final View selectedOverlay;
|
private final View selectOn;
|
||||||
|
private final View selectOff;
|
||||||
|
private final View selectOverlay;
|
||||||
|
|
||||||
ItemViewHolder(@NonNull View itemView) {
|
ItemViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
thumbnail = itemView.findViewById(R.id.mediapicker_image_item_thumbnail);
|
thumbnail = itemView.findViewById(R.id.mediapicker_image_item_thumbnail);
|
||||||
playOverlay = itemView.findViewById(R.id.mediapicker_play_overlay);
|
playOverlay = itemView.findViewById(R.id.mediapicker_play_overlay);
|
||||||
selectedOverlay = itemView.findViewById(R.id.mediapicker_selected);
|
selectOn = itemView.findViewById(R.id.mediapicker_select_on);
|
||||||
|
selectOff = itemView.findViewById(R.id.mediapicker_select_off);
|
||||||
|
selectOverlay = itemView.findViewById(R.id.mediapicker_select_overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(@NonNull Media media, boolean multiSelect, Set<Media> selected, int maxSelection, @NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
|
void bind(@NonNull Media media, boolean multiSelect, Set<Media> selected, int maxSelection, @NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
|
||||||
@ -113,10 +114,13 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter<MediaPickerItem
|
|||||||
.into(thumbnail);
|
.into(thumbnail);
|
||||||
|
|
||||||
playOverlay.setVisibility(MediaUtil.isVideoType(media.getMimeType()) ? View.VISIBLE : View.GONE);
|
playOverlay.setVisibility(MediaUtil.isVideoType(media.getMimeType()) ? View.VISIBLE : View.GONE);
|
||||||
selectedOverlay.setVisibility(selected.contains(media) ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
if (selected.isEmpty() && !multiSelect) {
|
if (selected.isEmpty() && !multiSelect) {
|
||||||
itemView.setOnClickListener(v -> eventListener.onMediaChosen(media));
|
itemView.setOnClickListener(v -> eventListener.onMediaChosen(media));
|
||||||
|
selectOn.setVisibility(View.GONE);
|
||||||
|
selectOff.setVisibility(View.GONE);
|
||||||
|
selectOverlay.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (maxSelection > 1) {
|
if (maxSelection > 1) {
|
||||||
itemView.setOnLongClickListener(v -> {
|
itemView.setOnLongClickListener(v -> {
|
||||||
selected.add(media);
|
selected.add(media);
|
||||||
@ -125,11 +129,17 @@ public class MediaPickerItemAdapter extends RecyclerView.Adapter<MediaPickerItem
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (selected.contains(media)) {
|
} else if (selected.contains(media)) {
|
||||||
|
selectOff.setVisibility(View.VISIBLE);
|
||||||
|
selectOn.setVisibility(View.VISIBLE);
|
||||||
|
selectOverlay.setVisibility(View.VISIBLE);
|
||||||
itemView.setOnClickListener(v -> {
|
itemView.setOnClickListener(v -> {
|
||||||
selected.remove(media);
|
selected.remove(media);
|
||||||
eventListener.onMediaSelectionChanged(new ArrayList<>(selected));
|
eventListener.onMediaSelectionChanged(new ArrayList<>(selected));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
selectOff.setVisibility(View.VISIBLE);
|
||||||
|
selectOn.setVisibility(View.GONE);
|
||||||
|
selectOverlay.setVisibility(View.GONE);
|
||||||
itemView.setOnClickListener(v -> {
|
itemView.setOnClickListener(v -> {
|
||||||
if (selected.size() < maxSelection) {
|
if (selected.size() < maxSelection) {
|
||||||
selected.add(media);
|
selected.add(media);
|
||||||
|
@ -50,8 +50,6 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
|||||||
private MediaPickerItemAdapter adapter;
|
private MediaPickerItemAdapter adapter;
|
||||||
private Controller controller;
|
private Controller controller;
|
||||||
private GridLayoutManager layoutManager;
|
private GridLayoutManager layoutManager;
|
||||||
private ActionMode actionMode;
|
|
||||||
private ActionMode.Callback actionModeCallback;
|
|
||||||
|
|
||||||
public static MediaPickerItemFragment newInstance(@NonNull String bucketId, @NonNull String folderTitle, int maxSelection) {
|
public static MediaPickerItemFragment newInstance(@NonNull String bucketId, @NonNull String folderTitle, int maxSelection) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@ -70,11 +68,10 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
bucketId = getArguments().getString(KEY_BUCKET_ID);
|
bucketId = getArguments().getString(KEY_BUCKET_ID);
|
||||||
folderTitle = getArguments().getString(KEY_FOLDER_TITLE);
|
folderTitle = getArguments().getString(KEY_FOLDER_TITLE);
|
||||||
maxSelection = getArguments().getInt(KEY_MAX_SELECTION);
|
maxSelection = getArguments().getInt(KEY_MAX_SELECTION);
|
||||||
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
viewModel = ViewModelProviders.of(requireActivity(), new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
||||||
actionModeCallback = new ActionModeCallback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -114,6 +111,8 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModel.getMediaInBucket(requireContext(), bucketId).observe(this, adapter::setMedia);
|
viewModel.getMediaInBucket(requireContext(), bucketId).observe(this, adapter::setMedia);
|
||||||
|
|
||||||
|
initMediaObserver(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -125,16 +124,19 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onPrepareOptionsMenu(Menu menu) {
|
||||||
inflater.inflate(R.menu.mediapicker_default, 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
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.mediapicker_menu_add) {
|
if (item.getItemId() == R.id.mediapicker_menu_add) {
|
||||||
adapter.setForcedMultiSelect(true);
|
adapter.setForcedMultiSelect(true);
|
||||||
actionMode = ((AppCompatActivity) requireActivity()).startSupportActionMode(actionModeCallback);
|
viewModel.onMultiSelectStarted();
|
||||||
actionMode.setTitle(getResources().getString(R.string.MediaPickerItemFragment_tap_to_select));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -148,23 +150,13 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMediaChosen(@NonNull Media media) {
|
public void onMediaChosen(@NonNull Media media) {
|
||||||
controller.onMediaSelected(bucketId, Collections.singleton(media));
|
|
||||||
viewModel.onSelectedMediaChanged(requireContext(), Collections.singletonList(media));
|
viewModel.onSelectedMediaChanged(requireContext(), Collections.singletonList(media));
|
||||||
|
controller.onMediaSelected(bucketId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMediaSelectionChanged(@NonNull List<Media> selected) {
|
public void onMediaSelectionChanged(@NonNull List<Media> selected) {
|
||||||
adapter.notifyDataSetChanged();
|
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);
|
viewModel.onSelectedMediaChanged(requireContext(), selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +173,12 @@ public class MediaPickerItemFragment extends Fragment implements MediaPickerItem
|
|||||||
toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
|
toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initMediaObserver(@NonNull MediaSendViewModel viewModel) {
|
||||||
|
viewModel.getCountButtonState().observe(this, media -> {
|
||||||
|
requireActivity().invalidateOptionsMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void onScreenWidthChanged(int newWidth) {
|
private void onScreenWidthChanged(int newWidth) {
|
||||||
if (layoutManager != null) {
|
if (layoutManager != null) {
|
||||||
layoutManager.setSpanCount(newWidth / getResources().getDimensionPixelSize(R.dimen.media_picker_item_width));
|
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;
|
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<Media> 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 {
|
public interface Controller {
|
||||||
void onMediaSelected(@NonNull String bucketId, @NonNull Collection<Media> media);
|
void onMediaSelected(@NonNull String bucketId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,18 @@ import android.content.Intent;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
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.PassphraseRequiredActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.TransportOption;
|
import org.thoughtcrime.securesms.TransportOption;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
||||||
@ -23,6 +30,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encompasses the entire flow of sending media, starting from the selection process to the actual
|
* 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 final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
private Recipient recipient;
|
private Recipient recipient;
|
||||||
private String body;
|
|
||||||
private TransportOption transport;
|
private TransportOption transport;
|
||||||
private MediaSendViewModel viewModel;
|
private MediaSendViewModel viewModel;
|
||||||
|
|
||||||
|
private View countButton;
|
||||||
|
private TextView countButtonText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an intent to launch the media send flow starting with the picker.
|
* Get an intent to launch the media send flow starting with the picker.
|
||||||
*/
|
*/
|
||||||
@ -94,28 +104,42 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
setContentView(R.layout.mediapicker_activity);
|
setContentView(R.layout.mediasend_activity);
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
return;
|
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);
|
viewModel = ViewModelProviders.of(this, new MediaSendViewModel.Factory(new MediaRepository())).get(MediaSendViewModel.class);
|
||||||
recipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true);
|
recipient = Recipient.from(this, Address.fromSerialized(getIntent().getStringExtra(KEY_ADDRESS)), true);
|
||||||
body = getIntent().getStringExtra(KEY_BODY);
|
|
||||||
transport = getIntent().getParcelableExtra(KEY_TRANSPORT);
|
transport = getIntent().getParcelableExtra(KEY_TRANSPORT);
|
||||||
|
|
||||||
viewModel.setMediaConstraints(transport.isSms() ? MediaConstraints.getMmsMediaConstraints(transport.getSimSubscriptionId().or(-1))
|
viewModel.setMediaConstraints(transport.isSms() ? MediaConstraints.getMmsMediaConstraints(transport.getSimSubscriptionId().or(-1))
|
||||||
: MediaConstraints.getPushMediaConstraints());
|
: MediaConstraints.getPushMediaConstraints());
|
||||||
|
|
||||||
|
viewModel.onBodyChanged(getIntent().getStringExtra(KEY_BODY));
|
||||||
|
|
||||||
List<Media> media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
|
List<Media> media = getIntent().getParcelableArrayListExtra(KEY_MEDIA);
|
||||||
|
|
||||||
if (!Util.isEmpty(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 {
|
} 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
|
@Override
|
||||||
@ -137,41 +161,34 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
public void onFolderSelected(@NonNull MediaFolder folder) {
|
public void onFolderSelected(@NonNull MediaFolder folder) {
|
||||||
viewModel.onFolderSelected(folder.getBucketId());
|
viewModel.onFolderSelected(folder.getBucketId());
|
||||||
|
|
||||||
MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(),
|
MediaPickerItemFragment fragment = MediaPickerItemFragment.newInstance(folder.getBucketId(), folder.getTitle(), transport.isSms() ? MAX_SMS :MAX_PUSH);
|
||||||
folder.getTitle(),
|
|
||||||
transport.isSms() ? MAX_SMS : MAX_PUSH);
|
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
|
.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||||
.replace(R.id.mediapicker_fragment_container, fragment, TAG_ITEM_PICKER)
|
.replace(R.id.mediasend_fragment_container, fragment, TAG_ITEM_PICKER)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMediaSelected(@NonNull String bucketId, @NonNull Collection<Media> media) {
|
public void onMediaSelected(@NonNull String bucketId) {
|
||||||
MediaSendFragment fragment = MediaSendFragment.newInstance(body, transport, dynamicLanguage.getCurrentLocale());
|
navigateToMediaSend(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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAddMediaClicked(@NonNull String bucketId) {
|
public void onAddMediaClicked(@NonNull String bucketId) {
|
||||||
|
// TODO: Get actual folder title somehow
|
||||||
MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient);
|
MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient);
|
||||||
MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId,
|
MediaPickerItemFragment itemFragment = MediaPickerItemFragment.newInstance(bucketId, "", transport.isSms() ? MAX_SMS : MAX_PUSH);
|
||||||
"",
|
|
||||||
transport.isSms() ? MAX_SMS : MAX_PUSH);
|
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
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)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
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)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
@ -214,20 +231,29 @@ public class MediaSendActivity extends PassphraseRequiredActionBarActivity imple
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateToMediaSend(List<Media> media, String body, TransportOption transport) {
|
private void initializeCountButtonObserver(@NonNull TransportOption transport, @NonNull Locale locale) {
|
||||||
viewModel.setInitialSelectedMedia(this, media);
|
viewModel.getCountButtonState().observe(this, buttonState -> {
|
||||||
|
if (buttonState == null) return;
|
||||||
|
|
||||||
MediaSendFragment sendFragment = MediaSendFragment.newInstance(body, transport, dynamicLanguage.getCurrentLocale());
|
countButton.setVisibility(buttonState.getVisibility() ? View.VISIBLE : View.GONE);
|
||||||
getSupportFragmentManager().beginTransaction()
|
countButton.setOnClickListener(v -> navigateToMediaSend(transport, locale));
|
||||||
.replace(R.id.mediapicker_fragment_container, sendFragment, TAG_SEND)
|
countButtonText.setText(String.valueOf(buttonState.getCount()));
|
||||||
.commit();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateToFolderPicker(@NonNull Recipient recipient) {
|
private void navigateToMediaSend(@NonNull TransportOption transport, @NonNull Locale locale) {
|
||||||
MediaPickerFolderFragment folderFragment = MediaPickerFolderFragment.newInstance(recipient);
|
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()
|
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();
|
.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
|
|
||||||
private static final String TAG = MediaSendFragment.class.getSimpleName();
|
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_TRANSPORT = "transport";
|
||||||
private static final String KEY_LOCALE = "locale";
|
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();
|
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();
|
Bundle args = new Bundle();
|
||||||
args.putString(KEY_BODY, body);
|
|
||||||
args.putParcelable(KEY_TRANSPORT, transport);
|
args.putParcelable(KEY_TRANSPORT, transport);
|
||||||
args.putSerializable(KEY_LOCALE, locale);
|
args.putSerializable(KEY_LOCALE, locale);
|
||||||
|
|
||||||
@ -134,9 +132,6 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
locale = (Locale) getArguments().getSerializable(KEY_LOCALE);
|
locale = (Locale) getArguments().getSerializable(KEY_LOCALE);
|
||||||
|
|
||||||
initViewModel();
|
initViewModel();
|
||||||
|
|
||||||
requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
||||||
requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -181,7 +176,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
captionText.clearFocus();
|
captionText.clearFocus();
|
||||||
composeText.requestFocus();
|
composeText.requestFocus();
|
||||||
|
|
||||||
fragmentPagerAdapter = new MediaSendFragmentPagerAdapter(requireActivity().getSupportFragmentManager(), locale);
|
fragmentPagerAdapter = new MediaSendFragmentPagerAdapter(getChildFragmentManager(), locale);
|
||||||
fragmentPager.setAdapter(fragmentPagerAdapter);
|
fragmentPager.setAdapter(fragmentPagerAdapter);
|
||||||
|
|
||||||
FragmentPageChangeListener pageChangeListener = new FragmentPageChangeListener();
|
FragmentPageChangeListener pageChangeListener = new FragmentPageChangeListener();
|
||||||
@ -208,7 +203,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
sendButton.setTransport(transportOption);
|
sendButton.setTransport(transportOption);
|
||||||
sendButton.disableTransport(transportOption.getType() == TransportOption.Type.SMS ? TransportOption.Type.TEXTSECURE : TransportOption.Type.SMS);
|
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())) {
|
if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) {
|
||||||
@ -221,13 +216,25 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
fragmentPagerAdapter.restoreState(viewModel.getDrawState());
|
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
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
fragmentPagerAdapter.saveAllState();
|
||||||
viewModel.saveDrawState(fragmentPagerAdapter.getSavedState());
|
viewModel.saveDrawState(fragmentPagerAdapter.getSavedState());
|
||||||
|
viewModel.onImageEditorEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -328,11 +335,13 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl
|
|||||||
});
|
});
|
||||||
|
|
||||||
viewModel.getBucketId().observe(this, bucketId -> {
|
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);
|
addButton.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
addButton.setVisibility(View.VISIBLE);
|
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
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
presentCharactersRemaining();
|
presentCharactersRemaining();
|
||||||
|
viewModel.onBodyChanged(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,6 +9,7 @@ import android.support.v4.app.FragmentStatePagerAdapter;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
import org.thoughtcrime.securesms.scribbles.ScribbleFragment;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
@ -106,6 +107,15 @@ class MediaSendFragmentPagerAdapter extends FragmentStatePagerAdapter {
|
|||||||
return new HashMap<>(savedState);
|
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<Uri, Object> state) {
|
void restoreState(@NonNull Map<Uri, Object> state) {
|
||||||
savedState.clear();
|
savedState.clear();
|
||||||
savedState.putAll(state);
|
savedState.putAll(state);
|
||||||
|
@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
|
|||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -31,31 +30,37 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
private final MutableLiveData<List<Media>> selectedMedia;
|
private final MutableLiveData<List<Media>> selectedMedia;
|
||||||
private final MutableLiveData<List<Media>> bucketMedia;
|
private final MutableLiveData<List<Media>> bucketMedia;
|
||||||
private final MutableLiveData<Integer> position;
|
private final MutableLiveData<Integer> position;
|
||||||
private final MutableLiveData<Optional<String>> bucketId;
|
private final MutableLiveData<String> bucketId;
|
||||||
private final MutableLiveData<List<MediaFolder>> folders;
|
private final MutableLiveData<List<MediaFolder>> folders;
|
||||||
|
private final MutableLiveData<CountButtonState> countButtonState;
|
||||||
private final SingleLiveEvent<Error> error;
|
private final SingleLiveEvent<Error> error;
|
||||||
private final Map<Uri, Object> savedDrawState;
|
private final Map<Uri, Object> savedDrawState;
|
||||||
|
|
||||||
private MediaConstraints mediaConstraints;
|
private MediaConstraints mediaConstraints;
|
||||||
|
private CharSequence body;
|
||||||
|
private CountButtonState.Visibility countButtonVisibility;
|
||||||
|
|
||||||
private MediaSendViewModel(@NonNull MediaRepository repository) {
|
private MediaSendViewModel(@NonNull MediaRepository repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.selectedMedia = new MutableLiveData<>();
|
this.selectedMedia = new MutableLiveData<>();
|
||||||
this.bucketMedia = new MutableLiveData<>();
|
this.bucketMedia = new MutableLiveData<>();
|
||||||
this.position = new MutableLiveData<>();
|
this.position = new MutableLiveData<>();
|
||||||
this.bucketId = new MutableLiveData<>();
|
this.bucketId = new MutableLiveData<>();
|
||||||
this.folders = new MutableLiveData<>();
|
this.folders = new MutableLiveData<>();
|
||||||
this.error = new SingleLiveEvent<>();
|
this.countButtonState = new MutableLiveData<>();
|
||||||
this.savedDrawState = new HashMap<>();
|
this.error = new SingleLiveEvent<>();
|
||||||
|
this.savedDrawState = new HashMap<>();
|
||||||
|
this.countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||||
|
|
||||||
position.setValue(-1);
|
position.setValue(-1);
|
||||||
|
countButtonState.setValue(new CountButtonState(0, CountButtonState.Visibility.CONDITIONAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMediaConstraints(@NonNull MediaConstraints mediaConstraints) {
|
void setMediaConstraints(@NonNull MediaConstraints mediaConstraints) {
|
||||||
this.mediaConstraints = mediaConstraints;
|
this.mediaConstraints = mediaConstraints;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInitialSelectedMedia(@NonNull Context context, @NonNull List<Media> newMedia) {
|
void onSelectedMediaChanged(@NonNull Context context, @NonNull List<Media> newMedia) {
|
||||||
repository.getPopulatedMedia(context, newMedia, populatedMedia -> {
|
repository.getPopulatedMedia(context, newMedia, populatedMedia -> {
|
||||||
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
|
List<Media> filteredMedia = getFilteredMedia(context, populatedMedia, mediaConstraints);
|
||||||
|
|
||||||
@ -63,26 +68,48 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
error.postValue(Error.ITEM_TOO_LARGE);
|
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);
|
selectedMedia.postValue(filteredMedia);
|
||||||
bucketId.postValue(allBucketsPopulated ? computeBucketId(filteredMedia) : Optional.absent());
|
countButtonState.postValue(new CountButtonState(filteredMedia.size(), countButtonVisibility));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSelectedMediaChanged(@NonNull Context context, @NonNull List<Media> newMedia) {
|
void onMultiSelectStarted() {
|
||||||
List<Media> filteredMedia = getFilteredMedia(context, newMedia, mediaConstraints);
|
countButtonVisibility = CountButtonState.Visibility.FORCED_ON;
|
||||||
|
countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||||
|
}
|
||||||
|
|
||||||
if (filteredMedia.size() != newMedia.size()) {
|
void onImageEditorStarted() {
|
||||||
error.setValue(Error.ITEM_TOO_LARGE);
|
countButtonVisibility = CountButtonState.Visibility.FORCED_OFF;
|
||||||
}
|
countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||||
|
}
|
||||||
|
|
||||||
selectedMedia.setValue(filteredMedia);
|
void onImageEditorEnded() {
|
||||||
position.setValue(filteredMedia.isEmpty() ? -1 : 0);
|
countButtonVisibility = CountButtonState.Visibility.CONDITIONAL;
|
||||||
|
countButtonState.postValue(new CountButtonState(getSelectedMediaOrDefault().size(), countButtonVisibility));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBodyChanged(@NonNull CharSequence body) {
|
||||||
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFolderSelected(@NonNull String bucketId) {
|
void onFolderSelected(@NonNull String bucketId) {
|
||||||
this.bucketId.setValue(Optional.of(bucketId));
|
this.bucketId.setValue(bucketId);
|
||||||
bucketMedia.setValue(Collections.emptyList());
|
bucketMedia.setValue(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +118,7 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onMediaItemRemoved(int position) {
|
void onMediaItemRemoved(int position) {
|
||||||
selectedMedia.getValue().remove(position);
|
getSelectedMediaOrDefault().remove(position);
|
||||||
selectedMedia.setValue(selectedMedia.getValue());
|
selectedMedia.setValue(selectedMedia.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,11 +137,11 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
return savedDrawState;
|
return savedDrawState;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<List<Media>> getSelectedMedia() {
|
@NonNull LiveData<List<Media>> getSelectedMedia() {
|
||||||
return selectedMedia;
|
return selectedMedia;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<List<Media>> getMediaInBucket(@NonNull Context context, @NonNull String bucketId) {
|
@NonNull LiveData<List<Media>> getMediaInBucket(@NonNull Context context, @NonNull String bucketId) {
|
||||||
repository.getMediaInBucket(context, bucketId, bucketMedia::postValue);
|
repository.getMediaInBucket(context, bucketId, bucketMedia::postValue);
|
||||||
return bucketMedia;
|
return bucketMedia;
|
||||||
}
|
}
|
||||||
@ -124,11 +151,19 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<CountButtonState> getCountButtonState() {
|
||||||
|
return countButtonState;
|
||||||
|
}
|
||||||
|
|
||||||
|
CharSequence getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
LiveData<Integer> getPosition() {
|
LiveData<Integer> getPosition() {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<Optional<String>> getBucketId() {
|
LiveData<String> getBucketId() {
|
||||||
return bucketId;
|
return bucketId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,17 +171,9 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> computeBucketId(@NonNull List<Media> media) {
|
private @NonNull List<Media> getSelectedMediaOrDefault() {
|
||||||
if (media.isEmpty() || !media.get(0).getBucketId().isPresent()) return Optional.absent();
|
return selectedMedia.getValue() == null ? Collections.emptyList()
|
||||||
|
: selectedMedia.getValue();
|
||||||
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<Media> getFilteredMedia(@NonNull Context context, @NonNull List<Media> media, @NonNull MediaConstraints mediaConstraints) {
|
private @NonNull List<Media> getFilteredMedia(@NonNull Context context, @NonNull List<Media> media, @NonNull MediaConstraints mediaConstraints) {
|
||||||
@ -165,6 +192,33 @@ class MediaSendViewModel extends ViewModel {
|
|||||||
ITEM_TOO_LARGE
|
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 {
|
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||||
|
|
||||||
private final MediaRepository repository;
|
private final MediaRepository repository;
|
||||||
|
@ -147,6 +147,10 @@ public class ScribbleFragment extends Fragment implements ScribbleHud.EventListe
|
|||||||
public void restoreState(@NonNull Object state) {
|
public void restoreState(@NonNull Object state) {
|
||||||
if (state instanceof ScribbleView.SavedState) {
|
if (state instanceof ScribbleView.SavedState) {
|
||||||
savedState = (ScribbleView.SavedState) state;
|
savedState = (ScribbleView.SavedState) state;
|
||||||
|
|
||||||
|
if (scribbleView != null) {
|
||||||
|
scribbleView.restoreState(savedState);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Received a bad saved state. Received class: " + state.getClass().getName());
|
Log.w(TAG, "Received a bad saved state. Received class: " + state.getClass().getName());
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
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.SimpleTarget;
|
||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
@ -87,6 +88,7 @@ public class ScribbleView extends FrameLayout {
|
|||||||
|
|
||||||
glideRequests.load(new DecryptableUri(uri))
|
glideRequests.load(new DecryptableUri(uri))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
.fitCenter()
|
.fitCenter()
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
}
|
}
|
||||||
|