From c244a98962e59686caa40fdc3d6afb0caa9823c6 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 20 Jan 2021 17:09:36 -0400 Subject: [PATCH] Finalize wallpaper UX. Co-authored-by: Greyson Parrelli Co-authored-by: Alan Evans --- .../ContactSelectionListFragment.java | 2 +- .../contacts/ContactSelectionListAdapter.java | 4 +- .../conversation/ConversationActivity.java | 5 +- .../conversation/ConversationAdapter.java | 41 ++++++-- .../conversation/ConversationFragment.java | 28 ++++-- .../ConversationReactionOverlay.java | 2 +- .../conversation/ConversationViewModel.java | 17 +++- .../conversation/LastSeenHeader.java | 2 +- .../ConversationListFragment.java | 2 +- .../ConversationListSearchAdapter.java | 4 +- .../securesms/recipients/Recipient.java | 4 + .../util/StickyHeaderDecoration.java | 12 ++- ...lerViewConcatenateAdapterStickyHeader.java | 8 +- .../ChatWallpaperAlignmentDecoration.java | 43 +++++++++ .../wallpaper/ChatWallpaperFragment.java | 80 +++++++++++++--- .../ChatWallpaperPreviewActivity.java | 23 ++++- .../wallpaper/ChatWallpaperRepository.java | 26 ++++- .../ChatWallpaperSelectionFragment.java | 30 +++++- .../wallpaper/ChatWallpaperViewModel.java | 45 +++++++-- .../securesms/wallpaper/WallpaperStorage.java | 19 ++-- .../wallpaper/crop/WallpaperCropActivity.java | 7 ++ .../crop/WallpaperCropRepository.java | 2 +- .../crop/WallpaperImageSelectionActivity.java | 3 + .../drawable/chat_wallpaper_preview_input.xml | 7 ++ .../main/res/layout/attachment_keyboard.xml | 1 + .../res/layout/chat_wallpaper_fragment.xml | 94 ++++++++++++++++++- .../chat_wallpaper_preview_activity.xml | 13 ++- .../res/layout/conversation_item_update.xml | 9 +- app/src/main/res/values/strings.xml | 12 ++- 29 files changed, 455 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperAlignmentDecoration.java create mode 100644 app/src/main/res/drawable/chat_wallpaper_preview_input.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java index c45cd721b7..41bf769d6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ContactSelectionListFragment.java @@ -309,7 +309,7 @@ public final class ContactSelectionListFragment extends LoggingFragment } recyclerView.setAdapter(concatenateAdapter); - recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true)); + recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0)); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java index 74530975e4..dd31237b5a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java @@ -266,12 +266,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter(); this.calendar = Calendar.getInstance(); this.digest = getMessageDigestOrThrow(); + this.hasWallpaper = recipient.hasWallpaper(); setHasStableIds(true); } @@ -243,7 +249,7 @@ public class ConversationAdapter recipient, searchQuery, conversationMessage == recordToPulse, - recipient.hasWallpaper()); + hasWallpaper); if (conversationMessage == recordToPulse) { recordToPulse = null; @@ -290,19 +296,27 @@ public class ConversationAdapter } @Override - public StickyHeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) { + public StickyHeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) { return new StickyHeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_item_header, parent, false)); } @Override - public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position) { + public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position, int type) { ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position)); viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateReceived())); - if (recipient.hasWallpaper()) { - viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8); - } else { - viewHolder.clearBackground(); + if (type == HEADER_TYPE_POPOVER_DATE) { + if (hasWallpaper) { + viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8); + } else { + viewHolder.setBackgroundRes(R.drawable.sticky_date_header_background); + } + } else if (type == HEADER_TYPE_INLINE_DATE) { + if (hasWallpaper) { + viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8); + } else { + viewHolder.clearBackground(); + } } } @@ -334,7 +348,7 @@ public class ConversationAdapter void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, int position) { viewHolder.setText(viewHolder.itemView.getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1))); - if (recipient.hasWallpaper()) { + if (hasWallpaper) { viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8); } else { viewHolder.clearBackground(); @@ -435,6 +449,17 @@ public class ConversationAdapter notifyDataSetChanged(); } + /** + * Lets the adapter know that the wallpaper state has changed. + */ + void onHasWallpaperChanged(boolean hasWallpaper) { + if (this.hasWallpaper != hasWallpaper) { + Log.d(TAG, "Resetting adapter due to wallpaper change."); + this.hasWallpaper = hasWallpaper; + notifyDataSetChanged(); + } + } + /** * Adds a record to a memory cache to allow it to be rendered immediately, as opposed to waiting * for a database change. diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index f979374bf3..d257ad76ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar; +import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.whispersystems.libsignal.util.guava.Optional; import java.io.IOException; @@ -171,7 +172,7 @@ public class ConversationFragment extends LoggingFragment { private Locale locale; private RecyclerView list; private RecyclerView.ItemDecoration lastSeenDecoration; - private RecyclerView.ItemDecoration stickyHeaderDecoration; + private RecyclerView.ItemDecoration popoverDateDecoration; private ViewSwitcher topLoadMoreView; private ViewSwitcher bottomLoadMoreView; private ConversationTypingView typingView; @@ -390,6 +391,13 @@ public class ConversationFragment extends LoggingFragment { snapToTopDataObserver.requestScrollPosition(position); } + public void onWallpaperChanged(@Nullable ChatWallpaper wallpaper) { + if (list != null) { + Log.d(TAG, "Notifying adapter that wallpaper state has changed."); + getListAdapter().onHasWallpaperChanged(wallpaper != null); + } + } + private int getStartPosition() { return conversationViewModel.getArgs().getStartingPosition(); } @@ -488,7 +496,7 @@ public class ConversationFragment extends LoggingFragment { this.threadId = conversationViewModel.getArgs().getThreadId(); this.markReadHelper = new MarkReadHelper(threadId, requireContext()); - conversationViewModel.onConversationDataAvailable(threadId, startingPosition); + conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, startingPosition); messageCountsViewModel.setThreadId(threadId); messageCountsViewModel.getUnreadMessagesCount().observe(getViewLifecycleOwner(), scrollToBottomButton::setUnreadCount); @@ -511,7 +519,7 @@ public class ConversationFragment extends LoggingFragment { ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get()); adapter.setPagingController(conversationViewModel.getPagingController()); list.setAdapter(adapter); - setStickyHeaderDecoration(adapter); + setPopoverDateDecoration(adapter); ConversationAdapter.initializePool(list.getRecycledViewPool()); adapter.registerAdapterDataObserver(snapToTopDataObserver); @@ -638,7 +646,7 @@ public class ConversationFragment extends LoggingFragment { messageRequestViewModel.setConversationInfo(recipient.getId(), threadId); snapToTopDataObserver.requestScrollPosition(0); - conversationViewModel.onConversationDataAvailable(threadId, -1); + conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, -1); messageCountsViewModel.setThreadId(threadId); initializeListAdapter(); } @@ -652,13 +660,13 @@ public class ConversationFragment extends LoggingFragment { } } - public void setStickyHeaderDecoration(@NonNull ConversationAdapter adapter) { - if (stickyHeaderDecoration != null) { - list.removeItemDecoration(stickyHeaderDecoration); + public void setPopoverDateDecoration(@NonNull ConversationAdapter adapter) { + if (popoverDateDecoration != null) { + list.removeItemDecoration(popoverDateDecoration); } - stickyHeaderDecoration = new StickyHeaderDecoration(adapter, false, false); - list.addItemDecoration(stickyHeaderDecoration); + popoverDateDecoration = new StickyHeaderDecoration(adapter, false, false, ConversationAdapter.HEADER_TYPE_INLINE_DATE); + list.addItemDecoration(popoverDateDecoration); } public void setLastSeen(long lastSeen) { @@ -1180,7 +1188,7 @@ public class ConversationFragment extends LoggingFragment { private void bindScrollHeader(StickyHeaderViewHolder headerViewHolder, int positionId) { if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) { - ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId); + ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId, ConversationAdapter.HEADER_TYPE_POPOVER_DATE); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java index 7c69ea2dcf..8ed563704f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -142,7 +142,7 @@ public final class ConversationReactionOverlay extends RelativeLayout { } public void setListVerticalTranslation(float translationY) { - maskView.setTargetParentTranslationY(translationY); + maskView.setTargetParentTranslationY(translationY - ViewUtil.getStatusBarHeight(maskView)); } public void show(@NonNull Activity activity, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java index d471847b26..b8e565b823 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationViewModel.java @@ -19,7 +19,11 @@ import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.MediaRepository; +import org.thoughtcrime.securesms.recipients.LiveRecipient; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; +import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.whispersystems.libsignal.util.Pair; import java.util.List; @@ -41,6 +45,8 @@ class ConversationViewModel extends ViewModel { private final LiveData canShowAsBubble; private final ProxyPagingController pagingController; private final DatabaseObserver.Observer messageObserver; + private final MutableLiveData recipientId; + private final LiveData wallpaper; private ConversationIntents.Args args; private int jumpToPosition; @@ -53,6 +59,7 @@ class ConversationViewModel extends ViewModel { this.threadId = new MutableLiveData<>(); this.showScrollButtons = new MutableLiveData<>(false); this.hasUnreadMentions = new MutableLiveData<>(false); + this.recipientId = new MutableLiveData<>(); this.pagingController = new ProxyPagingController(); this.messageObserver = pagingController::onDataInvalidated; @@ -97,6 +104,9 @@ class ConversationViewModel extends ViewModel { conversationMetadata = Transformations.switchMap(messages, m -> metadata); canShowAsBubble = LiveDataUtil.mapAsync(threadId, conversationRepository::canShowAsBubble); + wallpaper = Transformations.distinctUntilChanged(Transformations.map(Transformations.switchMap(recipientId, + id -> Recipient.live(id).getLiveData()), + Recipient::getWallpaper)); } void onAttachmentKeyboardOpen() { @@ -104,11 +114,12 @@ class ConversationViewModel extends ViewModel { } @MainThread - void onConversationDataAvailable(long threadId, int startingPosition) { + void onConversationDataAvailable(@NonNull RecipientId recipientId, long threadId, int startingPosition) { Log.d(TAG, "[onConversationDataAvailable] threadId: " + threadId + ", startingPosition: " + startingPosition); this.jumpToPosition = startingPosition; this.threadId.setValue(threadId); + this.recipientId.setValue(recipientId); } void clearThreadId() { @@ -128,6 +139,10 @@ class ConversationViewModel extends ViewModel { return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b)); } + @NonNull LiveData getWallpaper() { + return wallpaper; + } + void setHasUnreadMentions(boolean hasUnreadMentions) { this.hasUnreadMentions.setValue(hasUnreadMentions); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/LastSeenHeader.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/LastSeenHeader.java index 07ea5fa169..e224305d13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/LastSeenHeader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/LastSeenHeader.java @@ -17,7 +17,7 @@ class LastSeenHeader extends StickyHeaderDecoration { private final long lastSeenTimestamp; LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { - super(adapter, false, false); + super(adapter, false, false, ConversationAdapter.HEADER_TYPE_LAST_SEEN); this.adapter = adapter; this.lastSeenTimestamp = lastSeenTimestamp; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 34d29c9537..bb8d720d51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -481,7 +481,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode private void initializeListAdapters() { defaultAdapter = new ConversationListAdapter(GlideApp.with(this), this); searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault()); - searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false); + searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false, 0); setAdapter(defaultAdapter); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.java index 4b9cd45d23..bccbeb19f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.java @@ -94,13 +94,13 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter(); this.renderInline = renderInline; this.sticky = sticky; + this.type = type; } /** @@ -88,9 +90,9 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { if (headerHolder == null) { if (key != StickyHeaderAdapter.NO_HEADER_ID) { - headerHolder = adapter.onCreateHeaderViewHolder(parent, position); + headerHolder = adapter.onCreateHeaderViewHolder(parent, position, type); //noinspection unchecked - adapter.onBindHeaderViewHolder(headerHolder, position); + adapter.onBindHeaderViewHolder(headerHolder, position, type); } if (headerHolder == null) { @@ -221,7 +223,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { * @param position position in the adapter * @return a view holder for the created view */ - T onCreateHeaderViewHolder(ViewGroup parent, int position); + T onCreateHeaderViewHolder(ViewGroup parent, int position, int type); /** * Updates the header view to reflect the header data for the given position. @@ -229,7 +231,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { * @param viewHolder the header view holder * @param position the header's item position */ - void onBindHeaderViewHolder(T viewHolder, int position); + void onBindHeaderViewHolder(T viewHolder, int position, int type); int getItemCount(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapterStickyHeader.java b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapterStickyHeader.java index e6152507a9..b97d4e4064 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapterStickyHeader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/RecyclerViewConcatenateAdapterStickyHeader.java @@ -20,18 +20,18 @@ public final class RecyclerViewConcatenateAdapterStickyHeader extends Recycle } @Override - public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) { - return getForPosition(position).transform(p -> p.first().onCreateHeaderViewHolder(parent, p.second())).orNull(); + public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) { + return getForPosition(position).transform(p -> p.first().onCreateHeaderViewHolder(parent, p.second(), type)).orNull(); } @Override - public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int position, int type) { Optional> forPosition = getForPosition(position); if (forPosition.isPresent()) { Pair stickyHeaderAdapterIntegerPair = forPosition.get(); //noinspection unchecked - stickyHeaderAdapterIntegerPair.first().onBindHeaderViewHolder(viewHolder, stickyHeaderAdapterIntegerPair.second()); + stickyHeaderAdapterIntegerPair.first().onBindHeaderViewHolder(viewHolder, stickyHeaderAdapterIntegerPair.second(), type); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperAlignmentDecoration.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperAlignmentDecoration.java new file mode 100644 index 0000000000..34d55c9f1c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperAlignmentDecoration.java @@ -0,0 +1,43 @@ +package org.thoughtcrime.securesms.wallpaper; + +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +class ChatWallpaperAlignmentDecoration extends RecyclerView.ItemDecoration { + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + int itemPosition = parent.getChildAdapterPosition(view); + int itemCount = state.getItemCount(); + + if (itemCount > 0 && itemPosition == itemCount - 1) { + outRect.set(0, 0, 0, 0); + + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + int viewWidth = view.getMeasuredWidth() + params.rightMargin + params.leftMargin; + int availableWidth = (parent.getRight() - parent.getPaddingRight()) - (parent.getLeft() + parent.getPaddingLeft()); + int itemsPerRow = availableWidth / viewWidth; + + if (itemsPerRow == 1 || (itemPosition + 1) % itemsPerRow == 0) { + return; + } + + int extraCellsNeeded = itemsPerRow - ((itemPosition + 1) % itemsPerRow); + + setEnd(outRect, view.getLayoutDirection(), extraCellsNeeded * viewWidth); + } else { + super.getItemOffsets(outRect, view, parent, state); + } + } + + private void setEnd(@NonNull Rect outRect, int layoutDirection, int end) { + if (layoutDirection == View.LAYOUT_DIRECTION_LTR) { + outRect.right = end; + } else { + outRect.left = end; + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperFragment.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperFragment.java index 508552753f..dab1c8a2b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperFragment.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.wallpaper; +import android.app.AlertDialog; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -8,6 +9,7 @@ import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.widget.SwitchCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; @@ -18,6 +20,12 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.ThemeUtil; public class ChatWallpaperFragment extends Fragment { + + private boolean isSettingDimFromViewModel; + private View clearWallpaper; + private View resetAllWallpaper; + private View divider; + @Override public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.chat_wallpaper_fragment, container, false); @@ -30,8 +38,10 @@ public class ChatWallpaperFragment extends Fragment { View setWallpaper = view.findViewById(R.id.chat_wallpaper_set_wallpaper); SwitchCompat dimInNightMode = view.findViewById(R.id.chat_wallpaper_dark_theme_dims_wallpaper); View chatWallpaperDim = view.findViewById(R.id.chat_wallpaper_dim); - View clearWallpaper = view.findViewById(R.id.chat_wallpaper_clear_wallpaper); - View resetAllWallpaper = view.findViewById(R.id.chat_wallpaper_reset_all_wallpapers); + + clearWallpaper = view.findViewById(R.id.chat_wallpaper_clear_wallpaper); + resetAllWallpaper = view.findViewById(R.id.chat_wallpaper_reset_all_wallpapers); + divider = view.findViewById(R.id.chat_wallpaper_divider); viewModel.getCurrentWallpaper().observe(getViewLifecycleOwner(), wallpaper -> { if (wallpaper.isPresent()) { @@ -40,36 +50,78 @@ public class ChatWallpaperFragment extends Fragment { chatWallpaperPreview.setImageDrawable(null); chatWallpaperPreview.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.signal_background_primary)); } - - dimInNightMode.setEnabled(wallpaper.isPresent()); }); viewModel.getDimInDarkTheme().observe(getViewLifecycleOwner(), shouldDimInNightMode -> { if (shouldDimInNightMode != dimInNightMode.isChecked()) { + isSettingDimFromViewModel = true; dimInNightMode.setChecked(shouldDimInNightMode); + isSettingDimFromViewModel = false; } chatWallpaperDim.setAlpha(ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME); chatWallpaperDim.setVisibility(shouldDimInNightMode && ThemeUtil.isDarkTheme(requireContext()) ? View.VISIBLE : View.GONE); }); + viewModel.getEnableWallpaperControls().observe(getViewLifecycleOwner(), enableWallpaperControls -> { + dimInNightMode.setEnabled(enableWallpaperControls); + clearWallpaper.setVisibility(enableWallpaperControls ? View.VISIBLE : View.GONE); + updateDividerVisibility(); + }); + + chatWallpaperPreview.setOnClickListener(unused -> setWallpaper.performClick()); setWallpaper.setOnClickListener(unused -> Navigation.findNavController(view) .navigate(R.id.action_chatWallpaperFragment_to_chatWallpaperSelectionFragment)); - clearWallpaper.setOnClickListener(unused -> { - viewModel.setWallpaper(null); - viewModel.setDimInDarkTheme(false); - viewModel.saveWallpaperSelection(); - }); - resetAllWallpaper.setVisibility(viewModel.isGlobal() ? View.VISIBLE : View.GONE); + updateDividerVisibility(); + + clearWallpaper.setOnClickListener(unused -> { + confirmAction(R.string.ChatWallpaperFragment__clear_wallpaper_question_mark, + R.string.ChatWallpaperFragment__clear, + () -> { + viewModel.setWallpaper(null); + viewModel.setDimInDarkTheme(false); + viewModel.saveWallpaperSelection(); + }); + }); resetAllWallpaper.setOnClickListener(unused -> { - viewModel.setWallpaper(null); - viewModel.setDimInDarkTheme(false); - viewModel.resetAllWallpaper(); + confirmAction(R.string.ChatWallpaperFragment__reset_all_wallpapers_question_mark, + R.string.ChatWallpaperFragment__reset, + () -> { + viewModel.setWallpaper(null); + viewModel.setDimInDarkTheme(false); + viewModel.resetAllWallpaper(); + }); }); - dimInNightMode.setOnCheckedChangeListener((buttonView, isChecked) -> viewModel.setDimInDarkTheme(isChecked)); + dimInNightMode.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (!isSettingDimFromViewModel) { + viewModel.setDimInDarkTheme(isChecked); + } + }); + } + + private void updateDividerVisibility() { + if (clearWallpaper.getVisibility() == View.VISIBLE || resetAllWallpaper.getVisibility() == View.VISIBLE) { + divider.setVisibility(View.VISIBLE); + } else { + divider.setVisibility(View.GONE); + } + } + + private void confirmAction(@StringRes int title, @StringRes int positiveActionLabel, @NonNull Runnable onPositiveAction) { + new AlertDialog.Builder(requireContext()) + .setMessage(title) + .setPositiveButton(positiveActionLabel, (dialog, which) -> { + onPositiveAction.run(); + dialog.dismiss(); + }) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> { + dialog.dismiss(); + }) + .setCancelable(true) + .show(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java index f3ed8fe429..f41901cf0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperPreviewActivity.java @@ -2,24 +2,30 @@ package org.thoughtcrime.securesms.wallpaper; import android.content.Context; import android.content.Intent; +import android.graphics.PorterDuff; import android.os.Bundle; import android.view.View; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.view.ViewCompat; import androidx.viewpager2.widget.ViewPager2; import com.annimon.stream.Stream; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.FullscreenHelper; import org.thoughtcrime.securesms.util.MappingModel; +import org.thoughtcrime.securesms.util.WindowUtil; import java.util.Collections; @@ -27,14 +33,16 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity { public static final String EXTRA_CHAT_WALLPAPER = "extra.chat.wallpaper"; private static final String EXTRA_DIM_IN_DARK_MODE = "extra.dim.in.dark.mode"; + private static final String EXTRA_RECIPIENT_ID = "extra.recipient.id"; private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); - public static @NonNull Intent create(@NonNull Context context, @NonNull ChatWallpaper selection, boolean dimInDarkMode) { + public static @NonNull Intent create(@NonNull Context context, @NonNull ChatWallpaper selection, @NonNull RecipientId recipientId, boolean dimInDarkMode) { Intent intent = new Intent(context, ChatWallpaperPreviewActivity.class); intent.putExtra(EXTRA_CHAT_WALLPAPER, selection); intent.putExtra(EXTRA_DIM_IN_DARK_MODE, dimInDarkMode); + intent.putExtra(EXTRA_RECIPIENT_ID, recipientId); return intent; } @@ -52,6 +60,8 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity { ChatWallpaper selected = getIntent().getParcelableExtra(EXTRA_CHAT_WALLPAPER); boolean dim = getIntent().getBooleanExtra(EXTRA_DIM_IN_DARK_MODE, false); Toolbar toolbar = findViewById(R.id.toolbar); + View bubble1 = findViewById(R.id.preview_bubble_1); + TextView bubble2 = findViewById(R.id.preview_bubble_2_text); toolbar.setNavigationOnClickListener(unused -> { finish(); @@ -62,7 +72,7 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity { adapter.submitList(Collections.singletonList(new ChatWallpaperSelectionMappingModel(selected))); repository.getAllWallpaper(wallpapers -> adapter.submitList(Stream.of(wallpapers) - .map(wallpaper -> ChatWallpaperFactory.updateWithDimming(wallpaper, dim ? 1f : 0f)) + .map(wallpaper -> ChatWallpaperFactory.updateWithDimming(wallpaper, dim ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f)) .>map(ChatWallpaperSelectionMappingModel::new) .toList())); @@ -73,7 +83,16 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity { finish(); }); + RecipientId recipientId = getIntent().getParcelableExtra(EXTRA_RECIPIENT_ID); + if (recipientId != null) { + Recipient recipient = Recipient.live(recipientId).get(); + bubble1.getBackground().setColorFilter(recipient.getColor().toConversationColor(this), PorterDuff.Mode.SRC_IN); + bubble2.setText(getString(R.string.ChatWallpaperPreviewActivity__set_wallpaper_for_s, recipient.getDisplayName(this))); + } + new FullscreenHelper(this).showSystemUI(); + WindowUtil.setLightStatusBarFromTheme(this); + WindowUtil.setLightNavigationBarFromTheme(this); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperRepository.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperRepository.java index d9ce5f738a..dd0a773c03 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperRepository.java @@ -14,6 +14,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.concurrent.SerialExecutor; import org.whispersystems.libsignal.util.guava.Optional; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; @@ -24,14 +26,19 @@ class ChatWallpaperRepository { @MainThread @Nullable ChatWallpaper getCurrentWallpaper(@Nullable RecipientId recipientId) { if (recipientId != null) { - return Recipient.resolved(recipientId).getWallpaper(); + return Recipient.live(recipientId).get().getWallpaper(); } else { return SignalStore.wallpaper().getWallpaper(); } } void getAllWallpaper(@NonNull Consumer> consumer) { - consumer.accept(ChatWallpaper.BUILTINS); + EXECUTOR.execute(() -> { + List wallpapers = new ArrayList<>(ChatWallpaper.BUILTINS); + + wallpapers.addAll(WallpaperStorage.getAll(ApplicationDependencies.getApplication())); + consumer.accept(wallpapers); + }); } void saveWallpaper(@Nullable RecipientId recipientId, @Nullable ChatWallpaper chatWallpaper) { @@ -52,10 +59,21 @@ class ChatWallpaperRepository { }); } - void setDimInDarkTheme(@NonNull RecipientId recipientId, boolean dimInDarkTheme) { + void setDimInDarkTheme(@Nullable RecipientId recipientId, boolean dimInDarkTheme) { if (recipientId != null) { EXECUTOR.execute(() -> { - DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()).setDimWallpaperInDarkTheme(recipientId, dimInDarkTheme); + Recipient recipient = Recipient.resolved(recipientId); + if (recipient.hasOwnWallpaper()) { + DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()).setDimWallpaperInDarkTheme(recipientId, dimInDarkTheme); + } else if (recipient.hasWallpaper()) { + DatabaseFactory.getRecipientDatabase(ApplicationDependencies.getApplication()) + .setWallpaper(recipientId, + ChatWallpaperFactory.updateWithDimming(recipient.getWallpaper(), + dimInDarkTheme ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME + : 0f)); + } else { + throw new IllegalStateException("Unexpected call to setDimInDarkTheme, no wallpaper has been set on the given recipient or globally."); + } }); } else { SignalStore.wallpaper().setDimInDarkTheme(dimInDarkTheme); diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java index 86b4667bb3..a2091e60f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperSelectionFragment.java @@ -1,11 +1,13 @@ package org.thoughtcrime.securesms.wallpaper; +import android.Manifest; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,10 +16,12 @@ import androidx.lifecycle.ViewModelProviders; import androidx.navigation.Navigation; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.flexbox.AlignContent; import com.google.android.flexbox.FlexboxLayoutManager; import com.google.android.flexbox.JustifyContent; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.wallpaper.crop.WallpaperImageSelectionActivity; @@ -39,23 +43,30 @@ public class ChatWallpaperSelectionFragment extends Fragment { FlexboxLayoutManager flexboxLayoutManager = new FlexboxLayoutManager(requireContext()); chooseFromPhotos.setOnClickListener(unused -> { - startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER); + askForPermissionIfNeededAndLaunchPhotoSelection(); }); @SuppressWarnings("CodeBlock2Expr") ChatWallpaperSelectionAdapter adapter = new ChatWallpaperSelectionAdapter(chatWallpaper -> { - startActivityForResult(ChatWallpaperPreviewActivity.create(requireActivity(), chatWallpaper, viewModel.getDimInDarkTheme().getValue()), CHOOSE_WALLPAPER); + startActivityForResult(ChatWallpaperPreviewActivity.create(requireActivity(), chatWallpaper, viewModel.getRecipientId(), viewModel.getDimInDarkTheme().getValue()), CHOOSE_WALLPAPER); ActivityTransitionUtil.setSlideInTransition(requireActivity()); }); - flexboxLayoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); + flexboxLayoutManager.setJustifyContent(JustifyContent.CENTER); recyclerView.setLayoutManager(flexboxLayoutManager); recyclerView.setAdapter(adapter); + recyclerView.addItemDecoration(new ChatWallpaperAlignmentDecoration()); viewModel = ViewModelProviders.of(requireActivity()).get(ChatWallpaperViewModel.class); viewModel.getWallpapers().observe(getViewLifecycleOwner(), adapter::submitList); } + @Override + public void onResume() { + super.onResume(); + viewModel.refreshWallpaper(); + } + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == CHOOSE_WALLPAPER && resultCode == Activity.RESULT_OK && data != null) { @@ -67,4 +78,17 @@ public class ChatWallpaperSelectionFragment extends Fragment { super.onActivityResult(requestCode, resultCode, data); } } + + private void askForPermissionIfNeededAndLaunchPhotoSelection() { + Permissions.with(this) + .request(Manifest.permission.READ_EXTERNAL_STORAGE) + .ifNecessary() + .onAllGranted(() -> { + startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER); + ActivityTransitionUtil.setSlideInTransition(requireActivity()); + }) + .onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ChatWallpaperPreviewActivity__viewing_your_gallery_requires_the_storage_permission, Toast.LENGTH_SHORT) + .show()) + .execute(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperViewModel.java index 91d6d036fe..b756bf3840 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/ChatWallpaperViewModel.java @@ -9,6 +9,8 @@ import androidx.lifecycle.ViewModelProvider; import com.annimon.stream.Stream; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.MappingModel; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; @@ -19,10 +21,11 @@ import java.util.Objects; public class ChatWallpaperViewModel extends ViewModel { - private final ChatWallpaperRepository repository = new ChatWallpaperRepository(); - private final MutableLiveData> wallpaper = new MutableLiveData<>(); - private final MutableLiveData> builtins = new MutableLiveData<>(); - private final MutableLiveData dimInDarkTheme = new MutableLiveData<>(); + private final ChatWallpaperRepository repository = new ChatWallpaperRepository(); + private final MutableLiveData> wallpaper = new MutableLiveData<>(); + private final MutableLiveData> builtins = new MutableLiveData<>(); + private final MutableLiveData dimInDarkTheme = new MutableLiveData<>(); + private final MutableLiveData enableWallpaperControls = new MutableLiveData<>(); private final RecipientId recipientId; private ChatWallpaperViewModel(@Nullable RecipientId recipientId) { @@ -30,7 +33,11 @@ public class ChatWallpaperViewModel extends ViewModel { ChatWallpaper currentWallpaper = repository.getCurrentWallpaper(recipientId); dimInDarkTheme.setValue(currentWallpaper != null && currentWallpaper.getDimLevelForDarkTheme() > 0f); + enableWallpaperControls.setValue(hasClearableWallpaper()); wallpaper.setValue(Optional.fromNullable(currentWallpaper)); + } + + void refreshWallpaper() { repository.getAllWallpaper(builtins::postValue); } @@ -53,7 +60,18 @@ public class ChatWallpaperViewModel extends ViewModel { if (!wallpaper.isPresent()) { repository.saveWallpaper(recipientId, null); + + if (recipientId != null) { + ChatWallpaper globalWallpaper = SignalStore.wallpaper().getWallpaper(); + + this.wallpaper.setValue(Optional.fromNullable(globalWallpaper)); + this.dimInDarkTheme.setValue(globalWallpaper != null && globalWallpaper.getDimLevelForDarkTheme() > 0); + } + + enableWallpaperControls.setValue(false); return; + } else { + enableWallpaperControls.setValue(true); } Optional updated = wallpaper.transform(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkTheme ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f)); @@ -67,30 +85,39 @@ public class ChatWallpaperViewModel extends ViewModel { repository.resetAllWallpaper(); } - public @Nullable RecipientId getRecipientId() { + @Nullable RecipientId getRecipientId() { return recipientId; } - LiveData> getCurrentWallpaper() { + @NonNull LiveData> getCurrentWallpaper() { return wallpaper; } - LiveData>> getWallpapers() { + @NonNull LiveData>> getWallpapers() { return LiveDataUtil.combineLatest(builtins, dimInDarkTheme, (wallpapers, dimInDarkMode) -> Stream.of(wallpapers) - .map(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkMode ? 1f : 0f)) + .map(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkMode ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f)) .>map(ChatWallpaperSelectionMappingModel::new).toList() ); } - LiveData getDimInDarkTheme() { + @NonNull LiveData getDimInDarkTheme() { return dimInDarkTheme; } + @NonNull LiveData getEnableWallpaperControls() { + return enableWallpaperControls; + } + boolean isGlobal() { return recipientId == null; } + private boolean hasClearableWallpaper() { + return (isGlobal() && SignalStore.wallpaper().hasWallpaperSet()) || + (recipientId != null && Recipient.live(recipientId).get().hasOwnWallpaper()); + } + public static class Factory implements ViewModelProvider.Factory { private final RecipientId recipientId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/WallpaperStorage.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/WallpaperStorage.java index cfe7dd4ee6..70ab2ce4be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/WallpaperStorage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/WallpaperStorage.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -39,9 +40,9 @@ public final class WallpaperStorage { * Saves the provided input stream as a new wallpaper file. */ @WorkerThread - public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream) throws IOException { + public static @NonNull ChatWallpaper save(@NonNull Context context, @NonNull InputStream wallpaperStream, @NonNull String extension) throws IOException { File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); - File file = File.createTempFile(FILENAME_BASE, "", directory); + File file = File.createTempFile(FILENAME_BASE, "." + extension, directory); StreamUtil.copy(wallpaperStream, getOutputStream(context, file)); @@ -61,11 +62,15 @@ public final class WallpaperStorage { File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(FILENAME_BASE)); - return Stream.of(allFiles) - .map(File::getName) - .map(PartAuthority::getWallpaperUri) - .map(ChatWallpaperFactory::create) - .toList(); + if (allFiles != null) { + return Stream.of(allFiles) + .map(File::getName) + .map(PartAuthority::getWallpaperUri) + .map(ChatWallpaperFactory::create) + .toList(); + } else { + return Collections.emptyList(); + } } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropActivity.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropActivity.java index ba92814b59..895b596261 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropActivity.java @@ -17,6 +17,7 @@ import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; @@ -62,6 +63,12 @@ public final class WallpaperCropActivity extends BaseActivity { return intent; } + @Override + protected void attachBaseContext(@NonNull Context newBase) { + getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES); + super.attachBaseContext(newBase); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropRepository.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropRepository.java index 473ddac4a3..dcb856b8f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperCropRepository.java @@ -33,7 +33,7 @@ final class WallpaperCropRepository { @WorkerThread @NonNull ChatWallpaper setWallPaper(byte[] bytes) throws IOException { try (InputStream inputStream = new ByteArrayInputStream(bytes)) { - ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream); + ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream, "webp"); if (recipientId != null) { Log.i(TAG, "Setting image wallpaper for " + recipientId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java index cc16c1f12c..23197f2323 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java @@ -1,11 +1,13 @@ package org.thoughtcrime.securesms.wallpaper.crop; +import android.Manifest; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; @@ -23,6 +25,7 @@ public final class WallpaperImageSelectionActivity extends AppCompatActivity private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID"; private static final int CROP = 901; + @RequiresPermission(Manifest.permission.READ_EXTERNAL_STORAGE) public static Intent getIntent(@NonNull Context context, @Nullable RecipientId recipientId) { diff --git a/app/src/main/res/drawable/chat_wallpaper_preview_input.xml b/app/src/main/res/drawable/chat_wallpaper_preview_input.xml new file mode 100644 index 0000000000..9979888a7f --- /dev/null +++ b/app/src/main/res/drawable/chat_wallpaper_preview_input.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/layout/attachment_keyboard.xml b/app/src/main/res/layout/attachment_keyboard.xml index 356ef867f1..4b648189f9 100644 --- a/app/src/main/res/layout/attachment_keyboard.xml +++ b/app/src/main/res/layout/attachment_keyboard.xml @@ -9,6 +9,7 @@ + + + + + + + + + + + + + + + app:tint="@color/signal_background_secondary" /> + app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_divider" + tools:visibility="visible" /> @@ -95,10 +96,12 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/preview_bubble_1"> - @@ -107,7 +110,7 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:drawablePadding="4dp" - android:text="@string/ChatWallpaperPreviewActivity__10_49_am" + android:text="@string/DateUtils_just_now" android:textAppearance="@style/Signal.Text.Caption" android:textColor="@color/signal_text_secondary" app:drawableEndCompat="@drawable/ic_delivery_status_read" diff --git a/app/src/main/res/layout/conversation_item_update.xml b/app/src/main/res/layout/conversation_item_update.xml index 0068c177ff..669dc13e8b 100644 --- a/app/src/main/res/layout/conversation_item_update.xml +++ b/app/src/main/res/layout/conversation_item_update.xml @@ -8,14 +8,17 @@ android:background="@drawable/conversation_item_background" android:focusable="true" android:paddingStart="26dp" - android:paddingEnd="26dp"> + android:paddingEnd="26dp" + android:clipToPadding="false" + android:clipChildren="false"> Set wallpaper Dark theme dims wallpaper Clear wallpaper + Clear wallpaper? Reset all wallpapers + Reset all wallpapers? + Contact name + Reset + Clear Choose from photos @@ -2838,9 +2843,10 @@ Preview Set wallpaper - 10:49 am - This wallpaper will be set for all chats - Besides those you manually override + Swipe to preview more wallpapers + Set wallpaper for all chats + Set wallpaper for %1$s + Viewing your gallery requires the storage permission. Choose wallpaper image