Add selected photos access.
This commit is contained in:
parent
4f001a0c95
commit
57adab858c
19 changed files with 505 additions and 65 deletions
|
@ -83,6 +83,7 @@
|
|||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
|
||||
<application android:name=".ApplicationContext"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
|
@ -1032,7 +1032,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
findViewById(R.id.missing_permissions_container).setVisibility(View.GONE);
|
||||
})
|
||||
.onAnyDenied(() -> Toast.makeText(this, R.string.WebRtcCallActivity__signal_needs_camera_access_enable_video, Toast.LENGTH_LONG).show())
|
||||
.onAnyPermanentlyDenied(() -> showPermissionFragment(R.string.WebRtcCallActivity__allow_access_camera, R.string.WebRtcCallActivity__to_enable_video).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG))
|
||||
.onAnyPermanentlyDenied(() -> showPermissionFragment(R.string.WebRtcCallActivity__allow_access_camera, R.string.WebRtcCallActivity__to_enable_video, false).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG))
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
@ -1050,7 +1050,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
Toast.makeText(this, R.string.WebRtcCallActivity__signal_needs_microphone_start_call, Toast.LENGTH_LONG).show();
|
||||
handleDenyCall();
|
||||
})
|
||||
.onAnyPermanentlyDenied(() -> showPermissionFragment(R.string.WebRtcCallActivity__allow_access_microphone, R.string.WebRtcCallActivity__to_start_call).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG))
|
||||
.onAnyPermanentlyDenied(() -> showPermissionFragment(R.string.WebRtcCallActivity__allow_access_microphone, R.string.WebRtcCallActivity__to_start_call, false).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG))
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
@ -1065,11 +1065,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
.onAnyResult(() -> isAskingForPermission = false)
|
||||
.onSomePermanentlyDenied(deniedPermissions -> {
|
||||
if (deniedPermissions.containsAll(List.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))) {
|
||||
showPermissionFragment(R.string.WebRtcCallActivity__allow_access_camera_microphone, R.string.WebRtcCallActivity__to_start_call).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
showPermissionFragment(R.string.WebRtcCallActivity__allow_access_camera_microphone, R.string.WebRtcCallActivity__to_start_call, false).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
} else if (deniedPermissions.contains(Manifest.permission.CAMERA)) {
|
||||
showPermissionFragment(R.string.WebRtcCallActivity__allow_access_camera, R.string.WebRtcCallActivity__to_enable_video).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
showPermissionFragment(R.string.WebRtcCallActivity__allow_access_camera, R.string.WebRtcCallActivity__to_enable_video, false).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
} else {
|
||||
showPermissionFragment(R.string.WebRtcCallActivity__allow_access_microphone, R.string.WebRtcCallActivity__to_start_call).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
showPermissionFragment(R.string.WebRtcCallActivity__allow_access_microphone, R.string.WebRtcCallActivity__to_start_call, false).show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
})
|
||||
.onAllGranted(onGranted)
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -13,8 +16,10 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
|
@ -26,7 +31,8 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.InputView {
|
||||
|
||||
private static final List<AttachmentKeyboardButton> DEFAULT_BUTTONS = Arrays.asList(
|
||||
private static final int ANIMATION_DURATION = 150;
|
||||
private static final List<AttachmentKeyboardButton> DEFAULT_BUTTONS = Arrays.asList(
|
||||
AttachmentKeyboardButton.GALLERY,
|
||||
AttachmentKeyboardButton.FILE,
|
||||
AttachmentKeyboardButton.CONTACT,
|
||||
|
@ -39,9 +45,10 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
|
|||
private AttachmentKeyboardButtonAdapter buttonAdapter;
|
||||
private Callback callback;
|
||||
|
||||
private RecyclerView mediaList;
|
||||
private View permissionText;
|
||||
private View permissionButton;
|
||||
private RecyclerView mediaList;
|
||||
private TextView permissionText;
|
||||
private MaterialButton permissionButton;
|
||||
private MaterialButton manageButton;
|
||||
|
||||
public AttachmentKeyboard(@NonNull Context context) {
|
||||
super(context);
|
||||
|
@ -60,6 +67,7 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
|
|||
this.mediaList = findViewById(R.id.attachment_keyboard_media_list);
|
||||
this.permissionText = findViewById(R.id.attachment_keyboard_permission_text);
|
||||
this.permissionButton = findViewById(R.id.attachment_keyboard_permission_button);
|
||||
this.manageButton = findViewById(R.id.attachment_keyboard_manage_button);
|
||||
|
||||
RecyclerView buttonList = findViewById(R.id.attachment_keyboard_button_list);
|
||||
buttonList.setItemAnimator(null);
|
||||
|
@ -76,7 +84,17 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
|
|||
}
|
||||
});
|
||||
|
||||
manageButton.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
|
||||
manageButton.setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.onDisplayMoreContextMenu(v, true, false);
|
||||
}
|
||||
});
|
||||
|
||||
mediaList.setAdapter(mediaAdapter);
|
||||
mediaList.addOnScrollListener(new ScrollListener(manageButton.getMeasuredWidth()));
|
||||
buttonList.setAdapter(buttonAdapter);
|
||||
|
||||
buttonAdapter.registerAdapterDataObserver(new AttachmentButtonCenterHelper(buttonList));
|
||||
|
@ -100,14 +118,37 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
|
|||
}
|
||||
|
||||
public void onMediaChanged(@NonNull List<Media> media) {
|
||||
if (StorageUtil.canReadFromMediaStore()) {
|
||||
mediaAdapter.setMedia(media);
|
||||
if (StorageUtil.canReadAllFromMediaStore()) {
|
||||
mediaList.setVisibility(VISIBLE);
|
||||
mediaAdapter.setMedia(media, false);
|
||||
permissionButton.setVisibility(GONE);
|
||||
permissionText.setVisibility(GONE);
|
||||
} else {
|
||||
permissionButton.setVisibility(VISIBLE);
|
||||
manageButton.setVisibility(GONE);
|
||||
} else if (StorageUtil.canOnlyReadSelectedMediaStore() && media.isEmpty()) {
|
||||
mediaList.setVisibility(GONE);
|
||||
manageButton.setVisibility(GONE);
|
||||
permissionText.setVisibility(VISIBLE);
|
||||
|
||||
permissionText.setText(getContext().getString(R.string.AttachmentKeyboard_no_photos_found));
|
||||
permissionButton.setVisibility(VISIBLE);
|
||||
permissionButton.setText(getContext().getString(R.string.AttachmentKeyboard_manage));
|
||||
permissionButton.setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.onDisplayMoreContextMenu(v, true, true);
|
||||
}
|
||||
});
|
||||
} else if (StorageUtil.canOnlyReadSelectedMediaStore()) {
|
||||
mediaList.setVisibility(VISIBLE);
|
||||
mediaAdapter.setMedia(media, true);
|
||||
manageButton.setVisibility(VISIBLE);
|
||||
permissionText.setVisibility(GONE);
|
||||
permissionButton.setVisibility(GONE);
|
||||
} else {
|
||||
mediaList.setVisibility(GONE);
|
||||
manageButton.setVisibility(GONE);
|
||||
permissionButton.setVisibility(VISIBLE);
|
||||
permissionButton.setText(getContext().getString(R.string.AttachmentKeyboard_allow_access));
|
||||
permissionText.setVisibility(VISIBLE);
|
||||
permissionText.setText(getContext().getString(R.string.AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos));
|
||||
permissionButton.setOnClickListener(v -> {
|
||||
if (callback != null) {
|
||||
callback.onAttachmentPermissionsRequested();
|
||||
|
@ -144,9 +185,81 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
|
|||
return getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
private class ScrollListener extends RecyclerView.OnScrollListener {
|
||||
|
||||
private final int originalWidth;
|
||||
private final int iconWidth;
|
||||
|
||||
private ValueAnimator animator;
|
||||
private boolean isCollapsed;
|
||||
|
||||
public ScrollListener(int originalWidth) {
|
||||
this.originalWidth = originalWidth;
|
||||
this.iconWidth = manageButton.getIconSize() + manageButton.getPaddingLeft() + manageButton.getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
if (manageButton == null || recyclerView.getLayoutManager() == null || recyclerView.getAdapter() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
|
||||
View childView = layoutManager.getChildAt(0);
|
||||
int position = layoutManager.findLastVisibleItemPosition();
|
||||
|
||||
boolean visibleFirstChild = childView != null && childView.getTop() == 0 && layoutManager.getPosition(childView) == 0;
|
||||
boolean visibleLastChild = position == recyclerView.getAdapter().getItemCount() - 1;
|
||||
boolean shouldCollapse = !visibleFirstChild && !visibleLastChild;
|
||||
|
||||
if (shouldCollapse && !isCollapsed) {
|
||||
isCollapsed = true;
|
||||
if (animator != null) {
|
||||
animator.cancel();
|
||||
}
|
||||
animator = createWidthAnimator(manageButton, originalWidth, iconWidth, new AnimationCompleteListener() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
manageButton.setText("");
|
||||
}
|
||||
});
|
||||
animator.start();
|
||||
} else if (!shouldCollapse && isCollapsed) {
|
||||
isCollapsed = false;
|
||||
if (animator != null) {
|
||||
animator.cancel();
|
||||
}
|
||||
manageButton.setText(getContext().getString(R.string.AttachmentKeyboard_manage));
|
||||
animator = createWidthAnimator(manageButton, iconWidth, originalWidth, null);
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueAnimator createWidthAnimator(@NonNull View view,
|
||||
int originalWidth,
|
||||
int finalWidth,
|
||||
@Nullable AnimationCompleteListener onAnimationComplete)
|
||||
{
|
||||
ValueAnimator animator = ValueAnimator.ofInt(originalWidth, finalWidth).setDuration(ANIMATION_DURATION);
|
||||
|
||||
animator.addUpdateListener(animation -> {
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
params.width = (int) animation.getAnimatedValue();
|
||||
view.setLayoutParams(params);
|
||||
});
|
||||
|
||||
if (onAnimationComplete != null) {
|
||||
animator.addListener(onAnimationComplete);
|
||||
}
|
||||
|
||||
return animator;
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onAttachmentMediaClicked(@NonNull Media media);
|
||||
void onAttachmentSelectorClicked(@NonNull AttachmentKeyboardButton button);
|
||||
void onAttachmentPermissionsRequested();
|
||||
void onDisplayMoreContextMenu(View v, boolean showAbove, boolean showAtStart);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,12 +20,15 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyboardMediaAdapter.MediaViewHolder> {
|
||||
class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyboardMediaAdapter.ViewHolder> {
|
||||
|
||||
private final List<Media> media;
|
||||
private final RequestManager requestManager;
|
||||
private final Listener listener;
|
||||
private final StableIdGenerator<Media> idGenerator;
|
||||
private static final int VIEW_TYPE_MEDIA = 0;
|
||||
private static final int VIEW_TYPE_PLACEHOLDER = 1;
|
||||
|
||||
private final List<MediaContent> media;
|
||||
private final RequestManager requestManager;
|
||||
private final Listener listener;
|
||||
private final StableIdGenerator<MediaContent> idGenerator;
|
||||
|
||||
AttachmentKeyboardMediaAdapter(@NonNull RequestManager requestManager, @NonNull Listener listener) {
|
||||
this.requestManager = requestManager;
|
||||
|
@ -42,17 +45,21 @@ class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyb
|
|||
}
|
||||
|
||||
@Override
|
||||
public @NonNull MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new MediaViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.attachment_keyboad_media_item, parent, false));
|
||||
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return switch (viewType) {
|
||||
case VIEW_TYPE_MEDIA -> new MediaViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.attachment_keyboad_media_item, parent, false));
|
||||
case VIEW_TYPE_PLACEHOLDER -> new PlaceholderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.attachment_keyboad_media_placeholder_item, parent, false));
|
||||
default -> throw new IllegalArgumentException("Unsupported viewType: " + viewType);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.bind(media.get(position), requestManager, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull MediaViewHolder holder) {
|
||||
public void onViewRecycled(@NonNull ViewHolder holder) {
|
||||
holder.recycle();
|
||||
}
|
||||
|
||||
|
@ -61,9 +68,17 @@ class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyb
|
|||
return media.size();
|
||||
}
|
||||
|
||||
public void setMedia(@NonNull List<Media> media) {
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return media.get(position).isPlaceholder ? VIEW_TYPE_PLACEHOLDER : VIEW_TYPE_MEDIA;
|
||||
}
|
||||
|
||||
public void setMedia(@NonNull List<Media> media, boolean addFooter) {
|
||||
this.media.clear();
|
||||
this.media.addAll(media);
|
||||
this.media.addAll(media.stream().map(MediaContent::new).collect(java.util.stream.Collectors.toList()));
|
||||
if (addFooter) {
|
||||
this.media.add(new MediaContent(true));
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
@ -71,7 +86,36 @@ class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyb
|
|||
void onMediaClicked(@NonNull Media media);
|
||||
}
|
||||
|
||||
static class MediaViewHolder extends RecyclerView.ViewHolder {
|
||||
private class MediaContent {
|
||||
private Media media;
|
||||
private boolean isPlaceholder;
|
||||
|
||||
public MediaContent(Media media) {
|
||||
this.media = media;
|
||||
}
|
||||
|
||||
public MediaContent(boolean isPlaceholder) {
|
||||
this.isPlaceholder = isPlaceholder;
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void bind(@NonNull MediaContent media, @NonNull RequestManager requestManager, @NonNull Listener listener) {}
|
||||
|
||||
void recycle() {}
|
||||
}
|
||||
|
||||
static class PlaceholderViewHolder extends ViewHolder {
|
||||
public PlaceholderViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class MediaViewHolder extends ViewHolder {
|
||||
|
||||
private final ThumbnailView image;
|
||||
private final TextView duration;
|
||||
|
@ -84,7 +128,9 @@ class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyb
|
|||
videoIcon = itemView.findViewById(R.id.attachment_keyboard_item_video_icon);
|
||||
}
|
||||
|
||||
void bind(@NonNull Media media, @NonNull RequestManager requestManager, @NonNull Listener listener) {
|
||||
@Override
|
||||
void bind(@NonNull MediaContent mediaContent, @NonNull RequestManager requestManager, @NonNull Listener listener) {
|
||||
Media media = mediaContent.media;
|
||||
image.setImageResource(requestManager, media.getUri(), 400, 400);
|
||||
image.setOnClickListener(v -> listener.onMediaClicked(media));
|
||||
|
||||
|
@ -99,6 +145,7 @@ class AttachmentKeyboardMediaAdapter extends RecyclerView.Adapter<AttachmentKeyb
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void recycle() {
|
||||
image.setOnClickListener(null);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package org.thoughtcrime.securesms.conversation
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
|
||||
|
||||
/**
|
||||
* A context menu shown when handling selected media only permissions.
|
||||
* Will give users the ability to go to settings or to choose more media to give permission to
|
||||
*/
|
||||
object ManageContextMenu {
|
||||
|
||||
fun show(
|
||||
context: Context,
|
||||
anchorView: View,
|
||||
rootView: ViewGroup = anchorView.rootView as ViewGroup,
|
||||
showAbove: Boolean = false,
|
||||
showAtStart: Boolean = false,
|
||||
onSelectMore: () -> Unit,
|
||||
onSettings: () -> Unit
|
||||
) {
|
||||
show(
|
||||
context = context,
|
||||
anchorView = anchorView,
|
||||
rootView = rootView,
|
||||
showAbove = showAbove,
|
||||
showAtStart = showAtStart,
|
||||
callbacks = object : Callbacks {
|
||||
override fun onSelectMore() = onSelectMore()
|
||||
override fun onSettings() = onSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun show(
|
||||
context: Context,
|
||||
anchorView: View,
|
||||
rootView: ViewGroup = anchorView.rootView as ViewGroup,
|
||||
showAbove: Boolean = false,
|
||||
showAtStart: Boolean = false,
|
||||
callbacks: Callbacks
|
||||
) {
|
||||
val actions = mutableListOf<ActionItem>().apply {
|
||||
add(
|
||||
ActionItem(R.drawable.symbol_settings_android_24, context.getString(R.string.AttachmentKeyboard_go_to_settings)) {
|
||||
callbacks.onSettings()
|
||||
}
|
||||
)
|
||||
add(
|
||||
ActionItem(R.drawable.symbol_album_tilt_24, context.getString(R.string.AttachmentKeyboard_select_more_photos)) {
|
||||
callbacks.onSelectMore()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!showAbove) {
|
||||
actions.reverse()
|
||||
}
|
||||
|
||||
SignalContextMenu.Builder(anchorView, rootView)
|
||||
.preferredHorizontalPosition(if (showAtStart) SignalContextMenu.HorizontalPosition.START else SignalContextMenu.HorizontalPosition.END)
|
||||
.preferredVerticalPosition(if (showAbove) SignalContextMenu.VerticalPosition.ABOVE else SignalContextMenu.VerticalPosition.BELOW)
|
||||
.offsetY(DimensionUnit.DP.toPixels(8f).toInt())
|
||||
.show(actions)
|
||||
}
|
||||
|
||||
private interface Callbacks {
|
||||
fun onSelectMore()
|
||||
fun onSettings()
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.LoggingFragment
|
|||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.conversation.AttachmentKeyboard
|
||||
import org.thoughtcrime.securesms.conversation.AttachmentKeyboardButton
|
||||
import org.thoughtcrime.securesms.conversation.ManageContextMenu
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
|
@ -96,9 +97,32 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
|
|||
Permissions.with(requireParentFragment())
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.ifNecessary()
|
||||
.onAllGranted { viewModel.refreshRecentMedia() }
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio), null, R.string.AttachmentManager_signal_allow_storage, R.string.AttachmentManager_signal_to_show_photos, parentFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.AttachmentManager_signal_needs_storage_access, Toast.LENGTH_LONG).show() }
|
||||
.onAnyResult { viewModel.refreshRecentMedia() }
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio), null, R.string.AttachmentManager_signal_allow_storage, R.string.AttachmentManager_signal_to_show_photos, true, parentFragmentManager)
|
||||
.onSomeDenied {
|
||||
val deniedPermissions = PermissionCompat.getRequiredPermissionsForDenial()
|
||||
if (it.containsAll(deniedPermissions.toList())) {
|
||||
Toast.makeText(requireContext(), R.string.AttachmentManager_signal_needs_storage_access, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
.execute()
|
||||
}
|
||||
|
||||
override fun onDisplayMoreContextMenu(v: View, showAbove: Boolean, showAtStart: Boolean) {
|
||||
ManageContextMenu.show(
|
||||
context = requireContext(),
|
||||
anchorView = v,
|
||||
showAbove = showAbove,
|
||||
showAtStart = showAtStart,
|
||||
onSelectMore = { selectMorePhotos() },
|
||||
onSettings = { requireContext().startActivity(Permissions.getApplicationSettingsIntent(requireContext())) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectMorePhotos() {
|
||||
Permissions.with(requireParentFragment())
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.onAnyResult { viewModel.refreshRecentMedia() }
|
||||
.execute()
|
||||
}
|
||||
|
||||
|
|
|
@ -308,9 +308,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
|||
})
|
||||
.onSomePermanentlyDenied(deniedPermissions -> {
|
||||
if (deniedPermissions.containsAll(List.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))) {
|
||||
showPermissionFragment(R.string.CameraXFragment_allow_access_camera_microphone, R.string.CameraXFragment_to_capture_photos_videos).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
showPermissionFragment(R.string.CameraXFragment_allow_access_camera_microphone, R.string.CameraXFragment_to_capture_photos_videos, false).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
} else if (deniedPermissions.contains(Manifest.permission.CAMERA)) {
|
||||
showPermissionFragment(R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
showPermissionFragment(R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos, false).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
})
|
||||
.onSomeDenied(deniedPermissions -> {
|
||||
|
|
|
@ -55,7 +55,7 @@ public class MediaRepository {
|
|||
* Retrieves a list of folders that contain media.
|
||||
*/
|
||||
public void getFolders(@NonNull Context context, @NonNull Callback<List<MediaFolder>> callback) {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
if (!StorageUtil.canReadAnyFromMediaStore()) {
|
||||
Log.w(TAG, "No storage permissions!", new Throwable());
|
||||
callback.onComplete(Collections.emptyList());
|
||||
return;
|
||||
|
@ -69,7 +69,7 @@ public class MediaRepository {
|
|||
*/
|
||||
public Single<List<Media>> getRecentMedia() {
|
||||
return Single.<List<Media>>fromCallable(() -> {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
if (!StorageUtil.canReadAnyFromMediaStore()) {
|
||||
Log.w(TAG, "No storage permissions!", new Throwable());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ public class MediaRepository {
|
|||
* Retrieves a list of media items (images and videos) that are present int he specified bucket.
|
||||
*/
|
||||
public void getMediaInBucket(@NonNull Context context, @NonNull String bucketId, @NonNull Callback<List<Media>> callback) {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
if (!StorageUtil.canReadAnyFromMediaStore()) {
|
||||
Log.w(TAG, "No storage permissions!", new Throwable());
|
||||
callback.onComplete(Collections.emptyList());
|
||||
return;
|
||||
|
@ -106,7 +106,7 @@ public class MediaRepository {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
if (!StorageUtil.canReadAnyFromMediaStore()) {
|
||||
Log.w(TAG, "No storage permissions!", new Throwable());
|
||||
callback.onComplete(media);
|
||||
return;
|
||||
|
@ -117,7 +117,7 @@ public class MediaRepository {
|
|||
}
|
||||
|
||||
void getMostRecentItem(@NonNull Context context, @NonNull Callback<Optional<Media>> callback) {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
if (!StorageUtil.canReadAnyFromMediaStore()) {
|
||||
Log.w(TAG, "No storage permissions!", new Throwable());
|
||||
callback.onComplete(Optional.empty());
|
||||
return;
|
||||
|
|
|
@ -29,7 +29,7 @@ class MediaCaptureRepository(context: Context) {
|
|||
private val context: Context = context.applicationContext
|
||||
|
||||
fun getMostRecentItem(callback: (Media?) -> Unit) {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
if (!StorageUtil.canReadAnyFromMediaStore()) {
|
||||
Log.w(TAG, "Cannot read from storage.")
|
||||
callback(null)
|
||||
return
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.mediasend.v2.gallery
|
|||
import android.Manifest
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
@ -17,6 +18,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import org.signal.core.util.Stopwatch
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
|
||||
import org.thoughtcrime.securesms.conversation.ManageContextMenu
|
||||
import org.thoughtcrime.securesms.databinding.V2MediaGalleryFragmentBinding
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaRepository
|
||||
|
@ -24,6 +26,7 @@ import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
|
|||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
import org.thoughtcrime.securesms.util.StorageUtil
|
||||
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
@ -164,8 +167,6 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
|
|||
binding.mediaGalleryToolbar.title = state.bucketTitle ?: requireContext().getString(R.string.AttachmentKeyboard_gallery)
|
||||
}
|
||||
|
||||
binding.mediaGalleryAllowAccess.setOnClickListener { requestRequiredPermissions() }
|
||||
|
||||
val galleryItemsWithSelection = LiveDataUtil.combineLatest(
|
||||
viewModel.state.map { it.items },
|
||||
viewStateLiveData.map { it.selectedMedia }
|
||||
|
@ -180,20 +181,44 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
|
|||
}
|
||||
|
||||
galleryItemsWithSelection.observe(viewLifecycleOwner) {
|
||||
if (!Permissions.hasAll(requireContext(), *PermissionCompat.forImagesAndVideos())) {
|
||||
binding.mediaGalleryMissingPermissions.visibility = View.VISIBLE
|
||||
shouldEnableScrolling = false
|
||||
galleryAdapter.submitList((1..100).map { MediaGallerySelectableItem.PlaceholderModel() })
|
||||
} else {
|
||||
binding.mediaGalleryMissingPermissions.visibility = View.GONE
|
||||
if (StorageUtil.canReadAllFromMediaStore()) {
|
||||
binding.mediaGalleryMissingPermissions.visible = false
|
||||
binding.mediaGalleryManageContainer.visible = false
|
||||
shouldEnableScrolling = true
|
||||
galleryAdapter.submitList(it)
|
||||
} else if (StorageUtil.canOnlyReadSelectedMediaStore() && it.isEmpty()) {
|
||||
binding.mediaGalleryMissingPermissions.visible = true
|
||||
binding.mediaGalleryManageContainer.visible = false
|
||||
binding.mediaGalleryPermissionText.text = getString(R.string.MediaGalleryFragment__no_photos_found)
|
||||
binding.mediaGalleryAllowAccess.text = getString(R.string.AttachmentKeyboard_manage)
|
||||
binding.mediaGalleryAllowAccess.setOnClickListener { v -> showManageContextMenu(v, v.parent as ViewGroup, false, true) }
|
||||
shouldEnableScrolling = false
|
||||
galleryAdapter.submitList((1..100).map { MediaGallerySelectableItem.PlaceholderModel() })
|
||||
} else if (StorageUtil.canOnlyReadSelectedMediaStore()) {
|
||||
binding.mediaGalleryMissingPermissions.visible = false
|
||||
binding.mediaGalleryManageContainer.visible = true
|
||||
binding.mediaGalleryManageButton.setOnClickListener { v -> showManageContextMenu(v, v.rootView as ViewGroup, false, false) }
|
||||
shouldEnableScrolling = true
|
||||
galleryAdapter.submitList(it)
|
||||
} else {
|
||||
binding.mediaGalleryMissingPermissions.visible = true
|
||||
binding.mediaGalleryManageContainer.visible = false
|
||||
binding.mediaGalleryPermissionText.text = getString(R.string.AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos)
|
||||
binding.mediaGalleryAllowAccess.text = getString(R.string.AttachmentKeyboard_allow_access)
|
||||
binding.mediaGalleryAllowAccess.setOnClickListener { requestRequiredPermissions() }
|
||||
shouldEnableScrolling = false
|
||||
galleryAdapter.submitList((1..100).map { MediaGallerySelectableItem.PlaceholderModel() })
|
||||
}
|
||||
}
|
||||
|
||||
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refreshMediaGallery()
|
||||
}
|
||||
|
||||
private fun refreshMediaGallery() {
|
||||
viewModel.refreshMediaGallery()
|
||||
}
|
||||
|
@ -203,13 +228,37 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
|
|||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun showManageContextMenu(view: View, rootView: ViewGroup, showAbove: Boolean, showAtStart: Boolean) {
|
||||
ManageContextMenu.show(
|
||||
context = requireContext(),
|
||||
anchorView = view,
|
||||
rootView = rootView,
|
||||
showAbove = showAbove,
|
||||
showAtStart = showAtStart,
|
||||
onSelectMore = { selectMorePhotos() },
|
||||
onSettings = { requireContext().startActivity(Permissions.getApplicationSettingsIntent(requireContext())) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectMorePhotos() {
|
||||
Permissions.with(requireParentFragment())
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.onAnyResult { refreshMediaGallery() }
|
||||
.execute()
|
||||
}
|
||||
|
||||
private fun requestRequiredPermissions() {
|
||||
Permissions.with(this)
|
||||
.request(*PermissionCompat.forImagesAndVideos())
|
||||
.ifNecessary()
|
||||
.onAllGranted { refreshMediaGallery() }
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio), null, R.string.AttachmentManager_signal_allow_storage, R.string.AttachmentManager_signal_to_show_photos, parentFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.AttachmentManager_signal_needs_storage_access, Toast.LENGTH_LONG).show() }
|
||||
.onAnyResult { refreshMediaGallery() }
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio), null, R.string.AttachmentManager_signal_allow_storage, R.string.AttachmentManager_signal_to_show_photos, true, parentFragmentManager)
|
||||
.onSomeDenied {
|
||||
val deniedPermission = PermissionCompat.getRequiredPermissionsForDenial()
|
||||
if (it.containsAll(deniedPermission.toList())) {
|
||||
Toast.makeText(requireContext(), R.string.AttachmentManager_signal_needs_storage_access, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
.execute()
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,9 @@ import android.os.Build
|
|||
object PermissionCompat {
|
||||
@JvmStatic
|
||||
fun forImages(): Array<String> {
|
||||
return if (Build.VERSION.SDK_INT >= 33) {
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
|
||||
} else if (Build.VERSION.SDK_INT == 33) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
|
@ -23,7 +25,9 @@ object PermissionCompat {
|
|||
}
|
||||
|
||||
private fun forVideos(): Array<String> {
|
||||
return if (Build.VERSION.SDK_INT >= 33) {
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
|
||||
} else if (Build.VERSION.SDK_INT == 33) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_VIDEO)
|
||||
} else {
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
|
@ -34,4 +38,12 @@ object PermissionCompat {
|
|||
fun forImagesAndVideos(): Array<String> {
|
||||
return setOf(*(forImages() + forVideos())).toTypedArray()
|
||||
}
|
||||
|
||||
fun getRequiredPermissionsForDenial(): Array<String> {
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
|
||||
} else {
|
||||
forImagesAndVideos()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,16 +39,20 @@ private const val PLACEHOLDER = "__RADIO_BUTTON_PLACEHOLDER__"
|
|||
*/
|
||||
class PermissionDeniedBottomSheet private constructor() : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 0.66f
|
||||
|
||||
companion object {
|
||||
private const val ARG_TITLE = "argument.title_res"
|
||||
private const val ARG_SUBTITLE = "argument.subtitle_res"
|
||||
private const val ARG_USE_EXTENDED = "argument.use.extended"
|
||||
|
||||
@JvmStatic
|
||||
fun showPermissionFragment(titleRes: Int, subtitleRes: Int): ComposeBottomSheetDialogFragment {
|
||||
fun showPermissionFragment(titleRes: Int, subtitleRes: Int, useExtended: Boolean = false): ComposeBottomSheetDialogFragment {
|
||||
return PermissionDeniedBottomSheet().apply {
|
||||
arguments = bundleOf(
|
||||
ARG_TITLE to titleRes,
|
||||
ARG_SUBTITLE to subtitleRes
|
||||
ARG_SUBTITLE to subtitleRes,
|
||||
ARG_USE_EXTENDED to useExtended
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +63,7 @@ class PermissionDeniedBottomSheet private constructor() : ComposeBottomSheetDial
|
|||
PermissionDeniedSheetContent(
|
||||
titleRes = remember { requireArguments().getInt(ARG_TITLE) },
|
||||
subtitleRes = remember { requireArguments().getInt(ARG_SUBTITLE) },
|
||||
useExtended = remember { requireArguments().getBoolean(ARG_USE_EXTENDED) },
|
||||
onSettingsClicked = this::goToSettings
|
||||
)
|
||||
}
|
||||
|
@ -85,6 +90,7 @@ private fun PermissionDeniedSheetContentPreview() {
|
|||
private fun PermissionDeniedSheetContent(
|
||||
titleRes: Int,
|
||||
subtitleRes: Int,
|
||||
useExtended: Boolean = false,
|
||||
onSettingsClicked: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
|
@ -119,9 +125,18 @@ private fun PermissionDeniedSheetContent(
|
|||
modifier = Modifier.padding(bottom = 24.dp)
|
||||
)
|
||||
|
||||
val step2String = stringResource(id = R.string.PermissionDeniedBottomSheet__2_allow_permission, PLACEHOLDER)
|
||||
val (step2Text, step2InlineContent) = remember(step2String) {
|
||||
val parts = step2String.split(PLACEHOLDER)
|
||||
if (useExtended) {
|
||||
Text(
|
||||
text = stringResource(R.string.PermissionDeniedBottomSheet__2_tap_permissions),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
val stringId = if (useExtended) R.string.PermissionDeniedBottomSheet__3_allow_permission else R.string.PermissionDeniedBottomSheet__2_allow_permission
|
||||
val stepString = stringResource(id = stringId, PLACEHOLDER)
|
||||
val (stepText, stepInlineContent) = remember(stepString) {
|
||||
val parts = stepString.split(PLACEHOLDER)
|
||||
val annotatedString = buildAnnotatedString {
|
||||
append(parts[0])
|
||||
appendInlineContent("radio")
|
||||
|
@ -142,8 +157,8 @@ private fun PermissionDeniedSheetContent(
|
|||
}
|
||||
|
||||
Text(
|
||||
text = step2Text,
|
||||
inlineContent = step2InlineContent,
|
||||
text = stepText,
|
||||
inlineContent = stepInlineContent,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
|
|
@ -135,7 +135,11 @@ public class Permissions {
|
|||
}
|
||||
|
||||
public PermissionsBuilder withPermanentDenialDialog(@NonNull String message, @Nullable Runnable onDialogDismissed, int titleRes, int detailsRes, @Nullable FragmentManager fragmentManager) {
|
||||
return onAnyPermanentlyDenied(new SettingsDialogListener(permissionObject.getContext(), message, onDialogDismissed, titleRes, detailsRes, fragmentManager));
|
||||
return withPermanentDenialDialog(message, onDialogDismissed, titleRes, detailsRes, false, fragmentManager);
|
||||
}
|
||||
|
||||
public PermissionsBuilder withPermanentDenialDialog(@NonNull String message, @Nullable Runnable onDialogDismissed, int titleRes, int detailsRes, boolean useExtended, @Nullable FragmentManager fragmentManager) {
|
||||
return onAnyPermanentlyDenied(new SettingsDialogListener(permissionObject.getContext(), message, onDialogDismissed, titleRes, detailsRes, useExtended, fragmentManager));
|
||||
}
|
||||
|
||||
public PermissionsBuilder onAllGranted(Runnable allGrantedListener) {
|
||||
|
@ -402,14 +406,16 @@ public class Permissions {
|
|||
private final int titleRes;
|
||||
private final int detailsRes;
|
||||
private final boolean useBottomSheet;
|
||||
private final boolean useExtended;
|
||||
|
||||
SettingsDialogListener(Context context, String message, @Nullable Runnable onDialogDismissed, int titleRes, int detailsRes, @Nullable FragmentManager fragmentManager) {
|
||||
SettingsDialogListener(Context context, String message, @Nullable Runnable onDialogDismissed, int titleRes, int detailsRes, boolean useExtended, @Nullable FragmentManager fragmentManager) {
|
||||
this.message = message;
|
||||
this.context = new WeakReference<>(context);
|
||||
this.onDialogDismissed = onDialogDismissed;
|
||||
this.fragmentManager = new WeakReference<>(fragmentManager);
|
||||
this.titleRes = titleRes;
|
||||
this.detailsRes = detailsRes;
|
||||
this.useExtended = useExtended;
|
||||
this.useBottomSheet = fragmentManager != null;
|
||||
}
|
||||
|
||||
|
@ -420,7 +426,7 @@ public class Permissions {
|
|||
|
||||
if (context != null) {
|
||||
if (useBottomSheet && fragmentManager != null) {
|
||||
PermissionDeniedBottomSheet.showPermissionFragment(titleRes, detailsRes).show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
PermissionDeniedBottomSheet.showPermissionFragment(titleRes, detailsRes, useExtended).show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
} else if (!useBottomSheet){
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.Permissions_permission_required)
|
||||
|
|
|
@ -110,10 +110,18 @@ public class StorageUtil {
|
|||
Permissions.hasAll(AppDependencies.getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
public static boolean canReadFromMediaStore() {
|
||||
public static boolean canReadAnyFromMediaStore() {
|
||||
return Permissions.hasAny(AppDependencies.getApplication(), PermissionCompat.forImagesAndVideos());
|
||||
}
|
||||
|
||||
public static boolean canOnlyReadSelectedMediaStore() {
|
||||
return Build.VERSION.SDK_INT >= 34 && Permissions.hasAll(AppDependencies.getApplication(), Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED);
|
||||
}
|
||||
|
||||
public static boolean canReadAllFromMediaStore() {
|
||||
return Permissions.hasAll(AppDependencies.getApplication(), PermissionCompat.forImagesAndVideos());
|
||||
}
|
||||
|
||||
public static @NonNull Uri getVideoUri() {
|
||||
return MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<org.thoughtcrime.securesms.components.SquareFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:square_height="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:paddingBottom="8dp"
|
||||
app:autoSizeMaxTextSize="14sp"
|
||||
app:autoSizeMinTextSize="4sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
android:text="@string/AttachmentKeyboard_signal_has_limited_access" />
|
||||
|
||||
</org.thoughtcrime.securesms.components.SquareFrameLayout>
|
|
@ -27,6 +27,24 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/attachment_keyboard_manage_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="104dp"
|
||||
android:minHeight="36dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/attachment_keyboard_media_list"
|
||||
app:layout_constraintEnd_toEndOf="@id/attachment_keyboard_media_list"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="8dp"
|
||||
app:iconPadding="0dp"
|
||||
style="@style/Signal.Widget.Button.Large.Tonal"
|
||||
android:text="@string/AttachmentKeyboard_manage"
|
||||
android:maxLines="1"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/symbol_settings_android_24" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/attachment_keyboard_button_list"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -23,13 +23,42 @@
|
|||
app:title="@string/AttachmentKeyboard_gallery"
|
||||
app:titleTextAppearance="@style/Signal.Text.TitleLarge" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/media_gallery_manage_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="72dp"
|
||||
android:gravity="center"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/media_gallery_toolbar">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/Signal.Text.BodyMedium"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/AttachmentKeyboard_signal_has_limited_access" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/media_gallery_manage_button"
|
||||
style="@style/Signal.Widget.Button.Base.Tonal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="32dp"
|
||||
android:padding="0dp"
|
||||
android:text="@string/AttachmentKeyboard_manage" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/media_gallery_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@id/media_gallery_bottom_bar_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/media_gallery_toolbar"
|
||||
app:layout_constraintTop_toBottomOf="@id/media_gallery_manage_container"
|
||||
app:spanCount="4"
|
||||
tools:itemCount="36"
|
||||
tools:listitem="@layout/v2_media_gallery_item" />
|
||||
|
@ -52,6 +81,7 @@
|
|||
android:src="@drawable/permission_gallery" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/media_gallery_permission_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/AttachmentKeyboard_Signal_needs_permission_to_show_your_photos_and_videos"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<dimen name="signal_m3_toolbar_height">64dp</dimen>
|
||||
|
||||
<dimen name="min_keyboard_size">60dp</dimen>
|
||||
<dimen name="default_custom_keyboard_size">240dp</dimen>
|
||||
<dimen name="default_custom_keyboard_size">260dp</dimen>
|
||||
<dimen name="min_custom_keyboard_size">110dp</dimen>
|
||||
<dimen name="min_custom_keyboard_top_margin_portrait">170dp</dimen>
|
||||
<dimen name="min_custom_keyboard_top_margin_landscape_bubble">56dp</dimen>
|
||||
|
|
|
@ -89,6 +89,20 @@
|
|||
<!-- Text for a button prompting users to allow Signal access to their gallery storage -->
|
||||
<string name="AttachmentKeyboard_allow_access">Allow Access</string>
|
||||
<string name="AttachmentKeyboard_payment">Payment</string>
|
||||
<!-- Text in a button that allows users to manage which media Signal has access to -->
|
||||
<string name="AttachmentKeyboard_manage">Manage</string>
|
||||
<!-- Option in menu to select more photos that Signal will have access to -->
|
||||
<string name="AttachmentKeyboard_select_more_photos">Select more photos</string>
|
||||
<!-- Option in menu to go to settings -->
|
||||
<string name="AttachmentKeyboard_go_to_settings">Go to Settings</string>
|
||||
<!-- Text explaining that Signal has limited access to photos -->
|
||||
<string name="AttachmentKeyboard_signal_has_limited_access">Signal has limited access to photos or videos</string>
|
||||
<!-- Text shown when user has given no photos for Signal to access and an explanation on how to change those permissions -->
|
||||
<string name="AttachmentKeyboard_no_photos_found">No photos found, select photos and videos to appear here or change permissions</string>
|
||||
|
||||
<!-- MediaGalleryFragment -->
|
||||
<!-- Text describing that no photos or videos are currently found in the gallery and that Signal can only access media that the user allows -->
|
||||
<string name="MediaGalleryFragment__no_photos_found">No photos or videos found. Signal only has access to photos and videos you selected.</string>
|
||||
|
||||
<!-- AttachmentManager -->
|
||||
<string name="AttachmentManager_cant_open_media_selection">Can\'t find an app to select media.</string>
|
||||
|
@ -109,7 +123,7 @@
|
|||
<!-- Dialog description that will explain the steps needed to give gallery storage permission -->
|
||||
<string name="AttachmentManager_signal_to_show_photos">To show photos and videos:</string>
|
||||
<!-- Toast text explaining Signal\'s need for storage access -->
|
||||
<string name="AttachmentManager_signal_needs_storage_access">Signal needs storage access to show your photos and videos.</string>
|
||||
<string name="AttachmentManager_signal_needs_storage_access">Signal needs access to show your photos and videos.</string>
|
||||
|
||||
<!-- Alert dialog title to show the recipient has not activated payments -->
|
||||
<string name="AttachmentManager__not_activated_payments">%1$s hasn\'t activated Payments </string>
|
||||
|
@ -3734,6 +3748,10 @@
|
|||
<string name="PermissionDeniedBottomSheet__1_tap_settings_below">1. Tap “Settings” below</string>
|
||||
<!-- Sheet describing step 2 on how to give permissions by checking the permissions button in settings where %s will be replaced with an image of a checked button -->
|
||||
<string name="PermissionDeniedBottomSheet__2_allow_permission">2. %s Allow the permission</string>
|
||||
<!-- Sheet describing step 2 on how to give permissions by tapping permissions in their settings -->
|
||||
<string name="PermissionDeniedBottomSheet__2_tap_permissions">2. Tap “Permissions”</string>
|
||||
<!-- Sheet describing step 3 on how to give permissions for photos and videos in settings where %s will be replaced with an image of a checked button -->
|
||||
<string name="PermissionDeniedBottomSheet__3_allow_permission">3. %1$s Allow the “Photos and videos” permission</string>
|
||||
<!-- Label for button at the bottom of the sheet which opens the system permission settings -->
|
||||
<string name="PermissionDeniedBottomSheet__settings">Settings</string>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue