Finalize wallpaper UX.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
Co-authored-by: Alan Evans <alan@signal.org>
This commit is contained in:
Alex Hart
2021-01-20 17:09:36 -04:00
committed by Greyson Parrelli
parent a8ad1e718e
commit c244a98962
29 changed files with 455 additions and 90 deletions
@@ -309,7 +309,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
recyclerView.setAdapter(concatenateAdapter); recyclerView.setAdapter(concatenateAdapter);
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true)); recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true, 0));
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override @Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
@@ -266,12 +266,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
} }
@Override @Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) { public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false)); return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
} }
@Override @Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position)); ((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
} }
@@ -429,6 +429,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
setContentView(R.layout.conversation_activity); setContentView(R.layout.conversation_activity);
getWindow().getDecorView().setBackgroundResource(R.color.signal_background_primary); getWindow().getDecorView().setBackgroundResource(R.color.signal_background_primary);
WindowUtil.setLightNavigationBar(getWindow());
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale()); fragment = initFragment(R.id.fragment_content, new ConversationFragment(), dynamicLanguage.getCurrentLocale());
@@ -1989,6 +1990,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
} }
private void updateWallpaper(@Nullable ChatWallpaper chatWallpaper) { private void updateWallpaper(@Nullable ChatWallpaper chatWallpaper) {
Log.d(TAG, "Setting wallpaper.");
if (chatWallpaper != null) { if (chatWallpaper != null) {
chatWallpaper.loadInto(wallpaper); chatWallpaper.loadInto(wallpaper);
ChatWallpaperDimLevelUtil.applyDimLevelForNightMode(wallpaperDim, chatWallpaper); ChatWallpaperDimLevelUtil.applyDimLevelForNightMode(wallpaperDim, chatWallpaper);
@@ -1996,6 +1998,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
wallpaper.setImageDrawable(null); wallpaper.setImageDrawable(null);
wallpaperDim.setVisibility(View.GONE); wallpaperDim.setVisibility(View.GONE);
} }
fragment.onWallpaperChanged(chatWallpaper);
} }
protected void initializeActionBar() { protected void initializeActionBar() {
@@ -2105,6 +2108,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
this.viewModel = ViewModelProviders.of(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class); this.viewModel = ViewModelProviders.of(this, new ConversationViewModel.Factory()).get(ConversationViewModel.class);
this.viewModel.setArgs(args); this.viewModel.setArgs(args);
this.viewModel.getWallpaper().observe(this, this::updateWallpaper);
} }
private void initializeGroupViewModel() { private void initializeGroupViewModel() {
@@ -2290,7 +2294,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
updateReminders(); updateReminders();
updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId()); updateDefaultSubscriptionId(recipient.getDefaultSubscriptionId());
initializeSecurity(isSecureText, isDefaultSms); initializeSecurity(isSecureText, isDefaultSms);
updateWallpaper(recipient.getWallpaper());
if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) { if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) {
invalidateOptionsMenu(); invalidateOptionsMenu();
@@ -73,6 +73,10 @@ public class ConversationAdapter
private static final String TAG = Log.tag(ConversationAdapter.class); private static final String TAG = Log.tag(ConversationAdapter.class);
public static final int HEADER_TYPE_POPOVER_DATE = 1;
public static final int HEADER_TYPE_INLINE_DATE = 2;
public static final int HEADER_TYPE_LAST_SEEN = 3;
private static final int MESSAGE_TYPE_OUTGOING_MULTIMEDIA = 0; private static final int MESSAGE_TYPE_OUTGOING_MULTIMEDIA = 0;
private static final int MESSAGE_TYPE_OUTGOING_TEXT = 1; private static final int MESSAGE_TYPE_OUTGOING_TEXT = 1;
private static final int MESSAGE_TYPE_INCOMING_MULTIMEDIA = 2; private static final int MESSAGE_TYPE_INCOMING_MULTIMEDIA = 2;
@@ -102,6 +106,7 @@ public class ConversationAdapter
private View headerView; private View headerView;
private View footerView; private View footerView;
private PagingController pagingController; private PagingController pagingController;
private boolean hasWallpaper;
ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner, ConversationAdapter(@NonNull LifecycleOwner lifecycleOwner,
@NonNull GlideRequests glideRequests, @NonNull GlideRequests glideRequests,
@@ -132,6 +137,7 @@ public class ConversationAdapter
this.releasedFastRecords = new HashSet<>(); this.releasedFastRecords = new HashSet<>();
this.calendar = Calendar.getInstance(); this.calendar = Calendar.getInstance();
this.digest = getMessageDigestOrThrow(); this.digest = getMessageDigestOrThrow();
this.hasWallpaper = recipient.hasWallpaper();
setHasStableIds(true); setHasStableIds(true);
} }
@@ -243,7 +249,7 @@ public class ConversationAdapter
recipient, recipient,
searchQuery, searchQuery,
conversationMessage == recordToPulse, conversationMessage == recordToPulse,
recipient.hasWallpaper()); hasWallpaper);
if (conversationMessage == recordToPulse) { if (conversationMessage == recordToPulse) {
recordToPulse = null; recordToPulse = null;
@@ -290,19 +296,27 @@ public class ConversationAdapter
} }
@Override @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)); return new StickyHeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_item_header, parent, false));
} }
@Override @Override
public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position) { public void onBindHeaderViewHolder(StickyHeaderViewHolder viewHolder, int position, int type) {
ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position)); ConversationMessage conversationMessage = Objects.requireNonNull(getItem(position));
viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateReceived())); viewHolder.setText(DateUtils.getRelativeDate(viewHolder.itemView.getContext(), locale, conversationMessage.getMessageRecord().getDateReceived()));
if (recipient.hasWallpaper()) { if (type == HEADER_TYPE_POPOVER_DATE) {
viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8); if (hasWallpaper) {
} else { viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
viewHolder.clearBackground(); } 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) { void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, int position) {
viewHolder.setText(viewHolder.itemView.getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1))); 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); viewHolder.setBackgroundRes(R.drawable.wallpaper_bubble_background_8);
} else { } else {
viewHolder.clearBackground(); viewHolder.clearBackground();
@@ -435,6 +449,17 @@ public class ConversationAdapter
notifyDataSetChanged(); 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 * Adds a record to a memory cache to allow it to be rendered immediately, as opposed to waiting
* for a database change. * for a database change.
@@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar; import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
@@ -171,7 +172,7 @@ public class ConversationFragment extends LoggingFragment {
private Locale locale; private Locale locale;
private RecyclerView list; private RecyclerView list;
private RecyclerView.ItemDecoration lastSeenDecoration; private RecyclerView.ItemDecoration lastSeenDecoration;
private RecyclerView.ItemDecoration stickyHeaderDecoration; private RecyclerView.ItemDecoration popoverDateDecoration;
private ViewSwitcher topLoadMoreView; private ViewSwitcher topLoadMoreView;
private ViewSwitcher bottomLoadMoreView; private ViewSwitcher bottomLoadMoreView;
private ConversationTypingView typingView; private ConversationTypingView typingView;
@@ -390,6 +391,13 @@ public class ConversationFragment extends LoggingFragment {
snapToTopDataObserver.requestScrollPosition(position); 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() { private int getStartPosition() {
return conversationViewModel.getArgs().getStartingPosition(); return conversationViewModel.getArgs().getStartingPosition();
} }
@@ -488,7 +496,7 @@ public class ConversationFragment extends LoggingFragment {
this.threadId = conversationViewModel.getArgs().getThreadId(); this.threadId = conversationViewModel.getArgs().getThreadId();
this.markReadHelper = new MarkReadHelper(threadId, requireContext()); this.markReadHelper = new MarkReadHelper(threadId, requireContext());
conversationViewModel.onConversationDataAvailable(threadId, startingPosition); conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, startingPosition);
messageCountsViewModel.setThreadId(threadId); messageCountsViewModel.setThreadId(threadId);
messageCountsViewModel.getUnreadMessagesCount().observe(getViewLifecycleOwner(), scrollToBottomButton::setUnreadCount); 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()); ConversationAdapter adapter = new ConversationAdapter(this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
adapter.setPagingController(conversationViewModel.getPagingController()); adapter.setPagingController(conversationViewModel.getPagingController());
list.setAdapter(adapter); list.setAdapter(adapter);
setStickyHeaderDecoration(adapter); setPopoverDateDecoration(adapter);
ConversationAdapter.initializePool(list.getRecycledViewPool()); ConversationAdapter.initializePool(list.getRecycledViewPool());
adapter.registerAdapterDataObserver(snapToTopDataObserver); adapter.registerAdapterDataObserver(snapToTopDataObserver);
@@ -638,7 +646,7 @@ public class ConversationFragment extends LoggingFragment {
messageRequestViewModel.setConversationInfo(recipient.getId(), threadId); messageRequestViewModel.setConversationInfo(recipient.getId(), threadId);
snapToTopDataObserver.requestScrollPosition(0); snapToTopDataObserver.requestScrollPosition(0);
conversationViewModel.onConversationDataAvailable(threadId, -1); conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, -1);
messageCountsViewModel.setThreadId(threadId); messageCountsViewModel.setThreadId(threadId);
initializeListAdapter(); initializeListAdapter();
} }
@@ -652,13 +660,13 @@ public class ConversationFragment extends LoggingFragment {
} }
} }
public void setStickyHeaderDecoration(@NonNull ConversationAdapter adapter) { public void setPopoverDateDecoration(@NonNull ConversationAdapter adapter) {
if (stickyHeaderDecoration != null) { if (popoverDateDecoration != null) {
list.removeItemDecoration(stickyHeaderDecoration); list.removeItemDecoration(popoverDateDecoration);
} }
stickyHeaderDecoration = new StickyHeaderDecoration(adapter, false, false); popoverDateDecoration = new StickyHeaderDecoration(adapter, false, false, ConversationAdapter.HEADER_TYPE_INLINE_DATE);
list.addItemDecoration(stickyHeaderDecoration); list.addItemDecoration(popoverDateDecoration);
} }
public void setLastSeen(long lastSeen) { public void setLastSeen(long lastSeen) {
@@ -1180,7 +1188,7 @@ public class ConversationFragment extends LoggingFragment {
private void bindScrollHeader(StickyHeaderViewHolder headerViewHolder, int positionId) { private void bindScrollHeader(StickyHeaderViewHolder headerViewHolder, int positionId) {
if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) { if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) {
((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId); ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId, ConversationAdapter.HEADER_TYPE_POPOVER_DATE);
} }
} }
} }
@@ -142,7 +142,7 @@ public final class ConversationReactionOverlay extends RelativeLayout {
} }
public void setListVerticalTranslation(float translationY) { public void setListVerticalTranslation(float translationY) {
maskView.setTargetParentTranslationY(translationY); maskView.setTargetParentTranslationY(translationY - ViewUtil.getStatusBarHeight(maskView));
} }
public void show(@NonNull Activity activity, public void show(@NonNull Activity activity,
@@ -19,7 +19,11 @@ import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mediasend.MediaRepository; 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.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.Pair;
import java.util.List; import java.util.List;
@@ -41,6 +45,8 @@ class ConversationViewModel extends ViewModel {
private final LiveData<Boolean> canShowAsBubble; private final LiveData<Boolean> canShowAsBubble;
private final ProxyPagingController pagingController; private final ProxyPagingController pagingController;
private final DatabaseObserver.Observer messageObserver; private final DatabaseObserver.Observer messageObserver;
private final MutableLiveData<RecipientId> recipientId;
private final LiveData<ChatWallpaper> wallpaper;
private ConversationIntents.Args args; private ConversationIntents.Args args;
private int jumpToPosition; private int jumpToPosition;
@@ -53,6 +59,7 @@ class ConversationViewModel extends ViewModel {
this.threadId = new MutableLiveData<>(); this.threadId = new MutableLiveData<>();
this.showScrollButtons = new MutableLiveData<>(false); this.showScrollButtons = new MutableLiveData<>(false);
this.hasUnreadMentions = new MutableLiveData<>(false); this.hasUnreadMentions = new MutableLiveData<>(false);
this.recipientId = new MutableLiveData<>();
this.pagingController = new ProxyPagingController(); this.pagingController = new ProxyPagingController();
this.messageObserver = pagingController::onDataInvalidated; this.messageObserver = pagingController::onDataInvalidated;
@@ -97,6 +104,9 @@ class ConversationViewModel extends ViewModel {
conversationMetadata = Transformations.switchMap(messages, m -> metadata); conversationMetadata = Transformations.switchMap(messages, m -> metadata);
canShowAsBubble = LiveDataUtil.mapAsync(threadId, conversationRepository::canShowAsBubble); canShowAsBubble = LiveDataUtil.mapAsync(threadId, conversationRepository::canShowAsBubble);
wallpaper = Transformations.distinctUntilChanged(Transformations.map(Transformations.switchMap(recipientId,
id -> Recipient.live(id).getLiveData()),
Recipient::getWallpaper));
} }
void onAttachmentKeyboardOpen() { void onAttachmentKeyboardOpen() {
@@ -104,11 +114,12 @@ class ConversationViewModel extends ViewModel {
} }
@MainThread @MainThread
void onConversationDataAvailable(long threadId, int startingPosition) { void onConversationDataAvailable(@NonNull RecipientId recipientId, long threadId, int startingPosition) {
Log.d(TAG, "[onConversationDataAvailable] threadId: " + threadId + ", startingPosition: " + startingPosition); Log.d(TAG, "[onConversationDataAvailable] threadId: " + threadId + ", startingPosition: " + startingPosition);
this.jumpToPosition = startingPosition; this.jumpToPosition = startingPosition;
this.threadId.setValue(threadId); this.threadId.setValue(threadId);
this.recipientId.setValue(recipientId);
} }
void clearThreadId() { void clearThreadId() {
@@ -128,6 +139,10 @@ class ConversationViewModel extends ViewModel {
return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b)); return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b));
} }
@NonNull LiveData<ChatWallpaper> getWallpaper() {
return wallpaper;
}
void setHasUnreadMentions(boolean hasUnreadMentions) { void setHasUnreadMentions(boolean hasUnreadMentions) {
this.hasUnreadMentions.setValue(hasUnreadMentions); this.hasUnreadMentions.setValue(hasUnreadMentions);
} }
@@ -17,7 +17,7 @@ class LastSeenHeader extends StickyHeaderDecoration {
private final long lastSeenTimestamp; private final long lastSeenTimestamp;
LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) {
super(adapter, false, false); super(adapter, false, false, ConversationAdapter.HEADER_TYPE_LAST_SEEN);
this.adapter = adapter; this.adapter = adapter;
this.lastSeenTimestamp = lastSeenTimestamp; this.lastSeenTimestamp = lastSeenTimestamp;
} }
@@ -481,7 +481,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
private void initializeListAdapters() { private void initializeListAdapters() {
defaultAdapter = new ConversationListAdapter(GlideApp.with(this), this); defaultAdapter = new ConversationListAdapter(GlideApp.with(this), this);
searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault()); searchAdapter = new ConversationListSearchAdapter(GlideApp.with(this), this, Locale.getDefault());
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false); searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false, 0);
setAdapter(defaultAdapter); setAdapter(defaultAdapter);
@@ -94,13 +94,13 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<Conversation
} }
@Override @Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) { public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext()) return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.search_result_list_divider, parent, false)); .inflate(R.layout.search_result_list_divider, parent, false));
} }
@Override @Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
viewHolder.bind((int) getHeaderId(position)); viewHolder.bind((int) getHeaderId(position));
} }
@@ -855,6 +855,10 @@ public class Recipient {
} }
} }
public boolean hasOwnWallpaper() {
return wallpaper != null;
}
/** /**
* A cheap way to check if wallpaper is set without doing any unnecessary proto parsing. * A cheap way to check if wallpaper is set without doing any unnecessary proto parsing.
*/ */
@@ -30,15 +30,17 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
private final StickyHeaderAdapter adapter; private final StickyHeaderAdapter adapter;
private final boolean renderInline; private final boolean renderInline;
private final boolean sticky; private final boolean sticky;
private final int type;
/** /**
* @param adapter the sticky header adapter to use * @param adapter the sticky header adapter to use
*/ */
public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline, boolean sticky) { public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline, boolean sticky, int type) {
this.adapter = adapter; this.adapter = adapter;
this.headerCache = new HashMap<>(); this.headerCache = new HashMap<>();
this.renderInline = renderInline; this.renderInline = renderInline;
this.sticky = sticky; this.sticky = sticky;
this.type = type;
} }
/** /**
@@ -88,9 +90,9 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
if (headerHolder == null) { if (headerHolder == null) {
if (key != StickyHeaderAdapter.NO_HEADER_ID) { if (key != StickyHeaderAdapter.NO_HEADER_ID) {
headerHolder = adapter.onCreateHeaderViewHolder(parent, position); headerHolder = adapter.onCreateHeaderViewHolder(parent, position, type);
//noinspection unchecked //noinspection unchecked
adapter.onBindHeaderViewHolder(headerHolder, position); adapter.onBindHeaderViewHolder(headerHolder, position, type);
} }
if (headerHolder == null) { if (headerHolder == null) {
@@ -221,7 +223,7 @@ public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
* @param position position in the adapter * @param position position in the adapter
* @return a view holder for the created view * @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. * 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 viewHolder the header view holder
* @param position the header's item position * @param position the header's item position
*/ */
void onBindHeaderViewHolder(T viewHolder, int position); void onBindHeaderViewHolder(T viewHolder, int position, int type);
int getItemCount(); int getItemCount();
} }
@@ -20,18 +20,18 @@ public final class RecyclerViewConcatenateAdapterStickyHeader extends Recycle
} }
@Override @Override
public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position) { public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return getForPosition(position).transform(p -> p.first().onCreateHeaderViewHolder(parent, p.second())).orNull(); return getForPosition(position).transform(p -> p.first().onCreateHeaderViewHolder(parent, p.second(), type)).orNull();
} }
@Override @Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int position) { public void onBindHeaderViewHolder(RecyclerView.ViewHolder viewHolder, int position, int type) {
Optional<Pair<StickyHeaderDecoration.StickyHeaderAdapter, Integer>> forPosition = getForPosition(position); Optional<Pair<StickyHeaderDecoration.StickyHeaderAdapter, Integer>> forPosition = getForPosition(position);
if (forPosition.isPresent()) { if (forPosition.isPresent()) {
Pair<StickyHeaderDecoration.StickyHeaderAdapter, Integer> stickyHeaderAdapterIntegerPair = forPosition.get(); Pair<StickyHeaderDecoration.StickyHeaderAdapter, Integer> stickyHeaderAdapterIntegerPair = forPosition.get();
//noinspection unchecked //noinspection unchecked
stickyHeaderAdapterIntegerPair.first().onBindHeaderViewHolder(viewHolder, stickyHeaderAdapterIntegerPair.second()); stickyHeaderAdapterIntegerPair.first().onBindHeaderViewHolder(viewHolder, stickyHeaderAdapterIntegerPair.second(), type);
} }
} }
@@ -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;
}
}
}
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.wallpaper; package org.thoughtcrime.securesms.wallpaper;
import android.app.AlertDialog;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -8,6 +9,7 @@ import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -18,6 +20,12 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ThemeUtil;
public class ChatWallpaperFragment extends Fragment { public class ChatWallpaperFragment extends Fragment {
private boolean isSettingDimFromViewModel;
private View clearWallpaper;
private View resetAllWallpaper;
private View divider;
@Override @Override
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.chat_wallpaper_fragment, container, false); 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); View setWallpaper = view.findViewById(R.id.chat_wallpaper_set_wallpaper);
SwitchCompat dimInNightMode = view.findViewById(R.id.chat_wallpaper_dark_theme_dims_wallpaper); SwitchCompat dimInNightMode = view.findViewById(R.id.chat_wallpaper_dark_theme_dims_wallpaper);
View chatWallpaperDim = view.findViewById(R.id.chat_wallpaper_dim); 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 -> { viewModel.getCurrentWallpaper().observe(getViewLifecycleOwner(), wallpaper -> {
if (wallpaper.isPresent()) { if (wallpaper.isPresent()) {
@@ -40,36 +50,78 @@ public class ChatWallpaperFragment extends Fragment {
chatWallpaperPreview.setImageDrawable(null); chatWallpaperPreview.setImageDrawable(null);
chatWallpaperPreview.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.signal_background_primary)); chatWallpaperPreview.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.signal_background_primary));
} }
dimInNightMode.setEnabled(wallpaper.isPresent());
}); });
viewModel.getDimInDarkTheme().observe(getViewLifecycleOwner(), shouldDimInNightMode -> { viewModel.getDimInDarkTheme().observe(getViewLifecycleOwner(), shouldDimInNightMode -> {
if (shouldDimInNightMode != dimInNightMode.isChecked()) { if (shouldDimInNightMode != dimInNightMode.isChecked()) {
isSettingDimFromViewModel = true;
dimInNightMode.setChecked(shouldDimInNightMode); dimInNightMode.setChecked(shouldDimInNightMode);
isSettingDimFromViewModel = false;
} }
chatWallpaperDim.setAlpha(ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME); chatWallpaperDim.setAlpha(ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME);
chatWallpaperDim.setVisibility(shouldDimInNightMode && ThemeUtil.isDarkTheme(requireContext()) ? View.VISIBLE : View.GONE); 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) setWallpaper.setOnClickListener(unused -> Navigation.findNavController(view)
.navigate(R.id.action_chatWallpaperFragment_to_chatWallpaperSelectionFragment)); .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); 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 -> { resetAllWallpaper.setOnClickListener(unused -> {
viewModel.setWallpaper(null); confirmAction(R.string.ChatWallpaperFragment__reset_all_wallpapers_question_mark,
viewModel.setDimInDarkTheme(false); R.string.ChatWallpaperFragment__reset,
viewModel.resetAllWallpaper(); () -> {
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();
} }
} }
@@ -2,24 +2,30 @@ package org.thoughtcrime.securesms.wallpaper;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.viewpager2.widget.ViewPager2; import androidx.viewpager2.widget.ViewPager2;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.util.ActivityTransitionUtil;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FullscreenHelper; import org.thoughtcrime.securesms.util.FullscreenHelper;
import org.thoughtcrime.securesms.util.MappingModel; import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.WindowUtil;
import java.util.Collections; import java.util.Collections;
@@ -27,14 +33,16 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
public static final String EXTRA_CHAT_WALLPAPER = "extra.chat.wallpaper"; 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_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(); 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 intent = new Intent(context, ChatWallpaperPreviewActivity.class);
intent.putExtra(EXTRA_CHAT_WALLPAPER, selection); intent.putExtra(EXTRA_CHAT_WALLPAPER, selection);
intent.putExtra(EXTRA_DIM_IN_DARK_MODE, dimInDarkMode); intent.putExtra(EXTRA_DIM_IN_DARK_MODE, dimInDarkMode);
intent.putExtra(EXTRA_RECIPIENT_ID, recipientId);
return intent; return intent;
} }
@@ -52,6 +60,8 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
ChatWallpaper selected = getIntent().getParcelableExtra(EXTRA_CHAT_WALLPAPER); ChatWallpaper selected = getIntent().getParcelableExtra(EXTRA_CHAT_WALLPAPER);
boolean dim = getIntent().getBooleanExtra(EXTRA_DIM_IN_DARK_MODE, false); boolean dim = getIntent().getBooleanExtra(EXTRA_DIM_IN_DARK_MODE, false);
Toolbar toolbar = findViewById(R.id.toolbar); 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 -> { toolbar.setNavigationOnClickListener(unused -> {
finish(); finish();
@@ -62,7 +72,7 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
adapter.submitList(Collections.singletonList(new ChatWallpaperSelectionMappingModel(selected))); adapter.submitList(Collections.singletonList(new ChatWallpaperSelectionMappingModel(selected)));
repository.getAllWallpaper(wallpapers -> adapter.submitList(Stream.of(wallpapers) 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))
.<MappingModel<?>>map(ChatWallpaperSelectionMappingModel::new) .<MappingModel<?>>map(ChatWallpaperSelectionMappingModel::new)
.toList())); .toList()));
@@ -73,7 +83,16 @@ public class ChatWallpaperPreviewActivity extends PassphraseRequiredActivity {
finish(); 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(); new FullscreenHelper(this).showSystemUI();
WindowUtil.setLightStatusBarFromTheme(this);
WindowUtil.setLightNavigationBarFromTheme(this);
} }
@Override @Override
@@ -14,6 +14,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor; import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -24,14 +26,19 @@ class ChatWallpaperRepository {
@MainThread @MainThread
@Nullable ChatWallpaper getCurrentWallpaper(@Nullable RecipientId recipientId) { @Nullable ChatWallpaper getCurrentWallpaper(@Nullable RecipientId recipientId) {
if (recipientId != null) { if (recipientId != null) {
return Recipient.resolved(recipientId).getWallpaper(); return Recipient.live(recipientId).get().getWallpaper();
} else { } else {
return SignalStore.wallpaper().getWallpaper(); return SignalStore.wallpaper().getWallpaper();
} }
} }
void getAllWallpaper(@NonNull Consumer<List<ChatWallpaper>> consumer) { void getAllWallpaper(@NonNull Consumer<List<ChatWallpaper>> consumer) {
consumer.accept(ChatWallpaper.BUILTINS); EXECUTOR.execute(() -> {
List<ChatWallpaper> wallpapers = new ArrayList<>(ChatWallpaper.BUILTINS);
wallpapers.addAll(WallpaperStorage.getAll(ApplicationDependencies.getApplication()));
consumer.accept(wallpapers);
});
} }
void saveWallpaper(@Nullable RecipientId recipientId, @Nullable ChatWallpaper chatWallpaper) { 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) { if (recipientId != null) {
EXECUTOR.execute(() -> { 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 { } else {
SignalStore.wallpaper().setDimInDarkTheme(dimInDarkTheme); SignalStore.wallpaper().setDimInDarkTheme(dimInDarkTheme);
@@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.wallpaper; package org.thoughtcrime.securesms.wallpaper;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -14,10 +16,12 @@ import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.flexbox.AlignContent;
import com.google.android.flexbox.FlexboxLayoutManager; import com.google.android.flexbox.FlexboxLayoutManager;
import com.google.android.flexbox.JustifyContent; import com.google.android.flexbox.JustifyContent;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.ActivityTransitionUtil; import org.thoughtcrime.securesms.util.ActivityTransitionUtil;
import org.thoughtcrime.securesms.wallpaper.crop.WallpaperImageSelectionActivity; import org.thoughtcrime.securesms.wallpaper.crop.WallpaperImageSelectionActivity;
@@ -39,23 +43,30 @@ public class ChatWallpaperSelectionFragment extends Fragment {
FlexboxLayoutManager flexboxLayoutManager = new FlexboxLayoutManager(requireContext()); FlexboxLayoutManager flexboxLayoutManager = new FlexboxLayoutManager(requireContext());
chooseFromPhotos.setOnClickListener(unused -> { chooseFromPhotos.setOnClickListener(unused -> {
startActivityForResult(WallpaperImageSelectionActivity.getIntent(requireContext(), viewModel.getRecipientId()), CHOOSE_WALLPAPER); askForPermissionIfNeededAndLaunchPhotoSelection();
}); });
@SuppressWarnings("CodeBlock2Expr") @SuppressWarnings("CodeBlock2Expr")
ChatWallpaperSelectionAdapter adapter = new ChatWallpaperSelectionAdapter(chatWallpaper -> { 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()); ActivityTransitionUtil.setSlideInTransition(requireActivity());
}); });
flexboxLayoutManager.setJustifyContent(JustifyContent.SPACE_AROUND); flexboxLayoutManager.setJustifyContent(JustifyContent.CENTER);
recyclerView.setLayoutManager(flexboxLayoutManager); recyclerView.setLayoutManager(flexboxLayoutManager);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new ChatWallpaperAlignmentDecoration());
viewModel = ViewModelProviders.of(requireActivity()).get(ChatWallpaperViewModel.class); viewModel = ViewModelProviders.of(requireActivity()).get(ChatWallpaperViewModel.class);
viewModel.getWallpapers().observe(getViewLifecycleOwner(), adapter::submitList); viewModel.getWallpapers().observe(getViewLifecycleOwner(), adapter::submitList);
} }
@Override
public void onResume() {
super.onResume();
viewModel.refreshWallpaper();
}
@Override @Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == CHOOSE_WALLPAPER && resultCode == Activity.RESULT_OK && data != null) { if (requestCode == CHOOSE_WALLPAPER && resultCode == Activity.RESULT_OK && data != null) {
@@ -67,4 +78,17 @@ public class ChatWallpaperSelectionFragment extends Fragment {
super.onActivityResult(requestCode, resultCode, data); 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();
}
} }
@@ -9,6 +9,8 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream; 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.recipients.RecipientId;
import org.thoughtcrime.securesms.util.MappingModel; import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
@@ -19,10 +21,11 @@ import java.util.Objects;
public class ChatWallpaperViewModel extends ViewModel { public class ChatWallpaperViewModel extends ViewModel {
private final ChatWallpaperRepository repository = new ChatWallpaperRepository(); private final ChatWallpaperRepository repository = new ChatWallpaperRepository();
private final MutableLiveData<Optional<ChatWallpaper>> wallpaper = new MutableLiveData<>(); private final MutableLiveData<Optional<ChatWallpaper>> wallpaper = new MutableLiveData<>();
private final MutableLiveData<List<ChatWallpaper>> builtins = new MutableLiveData<>(); private final MutableLiveData<List<ChatWallpaper>> builtins = new MutableLiveData<>();
private final MutableLiveData<Boolean> dimInDarkTheme = new MutableLiveData<>(); private final MutableLiveData<Boolean> dimInDarkTheme = new MutableLiveData<>();
private final MutableLiveData<Boolean> enableWallpaperControls = new MutableLiveData<>();
private final RecipientId recipientId; private final RecipientId recipientId;
private ChatWallpaperViewModel(@Nullable RecipientId recipientId) { private ChatWallpaperViewModel(@Nullable RecipientId recipientId) {
@@ -30,7 +33,11 @@ public class ChatWallpaperViewModel extends ViewModel {
ChatWallpaper currentWallpaper = repository.getCurrentWallpaper(recipientId); ChatWallpaper currentWallpaper = repository.getCurrentWallpaper(recipientId);
dimInDarkTheme.setValue(currentWallpaper != null && currentWallpaper.getDimLevelForDarkTheme() > 0f); dimInDarkTheme.setValue(currentWallpaper != null && currentWallpaper.getDimLevelForDarkTheme() > 0f);
enableWallpaperControls.setValue(hasClearableWallpaper());
wallpaper.setValue(Optional.fromNullable(currentWallpaper)); wallpaper.setValue(Optional.fromNullable(currentWallpaper));
}
void refreshWallpaper() {
repository.getAllWallpaper(builtins::postValue); repository.getAllWallpaper(builtins::postValue);
} }
@@ -53,7 +60,18 @@ public class ChatWallpaperViewModel extends ViewModel {
if (!wallpaper.isPresent()) { if (!wallpaper.isPresent()) {
repository.saveWallpaper(recipientId, null); 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; return;
} else {
enableWallpaperControls.setValue(true);
} }
Optional<ChatWallpaper> updated = wallpaper.transform(paper -> ChatWallpaperFactory.updateWithDimming(paper, dimInDarkTheme ? ChatWallpaper.FIXED_DIM_LEVEL_FOR_DARK_THEME : 0f)); Optional<ChatWallpaper> 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(); repository.resetAllWallpaper();
} }
public @Nullable RecipientId getRecipientId() { @Nullable RecipientId getRecipientId() {
return recipientId; return recipientId;
} }
LiveData<Optional<ChatWallpaper>> getCurrentWallpaper() { @NonNull LiveData<Optional<ChatWallpaper>> getCurrentWallpaper() {
return wallpaper; return wallpaper;
} }
LiveData<List<MappingModel<?>>> getWallpapers() { @NonNull LiveData<List<MappingModel<?>>> getWallpapers() {
return LiveDataUtil.combineLatest(builtins, dimInDarkTheme, (wallpapers, dimInDarkMode) -> return LiveDataUtil.combineLatest(builtins, dimInDarkTheme, (wallpapers, dimInDarkMode) ->
Stream.of(wallpapers) 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))
.<MappingModel<?>>map(ChatWallpaperSelectionMappingModel::new).toList() .<MappingModel<?>>map(ChatWallpaperSelectionMappingModel::new).toList()
); );
} }
LiveData<Boolean> getDimInDarkTheme() { @NonNull LiveData<Boolean> getDimInDarkTheme() {
return dimInDarkTheme; return dimInDarkTheme;
} }
@NonNull LiveData<Boolean> getEnableWallpaperControls() {
return enableWallpaperControls;
}
boolean isGlobal() { boolean isGlobal() {
return recipientId == null; 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 { public static class Factory implements ViewModelProvider.Factory {
private final RecipientId recipientId; private final RecipientId recipientId;
@@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -39,9 +40,9 @@ public final class WallpaperStorage {
* Saves the provided input stream as a new wallpaper file. * Saves the provided input stream as a new wallpaper file.
*/ */
@WorkerThread @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 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)); StreamUtil.copy(wallpaperStream, getOutputStream(context, file));
@@ -61,11 +62,15 @@ public final class WallpaperStorage {
File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE); File directory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(FILENAME_BASE)); File[] allFiles = directory.listFiles(pathname -> pathname.getName().contains(FILENAME_BASE));
return Stream.of(allFiles) if (allFiles != null) {
.map(File::getName) return Stream.of(allFiles)
.map(PartAuthority::getWallpaperUri) .map(File::getName)
.map(ChatWallpaperFactory::create) .map(PartAuthority::getWallpaperUri)
.toList(); .map(ChatWallpaperFactory::create)
.toList();
} else {
return Collections.emptyList();
}
} }
/** /**
@@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.StyleRes; import androidx.annotation.StyleRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@@ -62,6 +63,12 @@ public final class WallpaperCropActivity extends BaseActivity {
return intent; return intent;
} }
@Override
protected void attachBaseContext(@NonNull Context newBase) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
super.attachBaseContext(newBase);
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -33,7 +33,7 @@ final class WallpaperCropRepository {
@WorkerThread @WorkerThread
@NonNull ChatWallpaper setWallPaper(byte[] bytes) throws IOException { @NonNull ChatWallpaper setWallPaper(byte[] bytes) throws IOException {
try (InputStream inputStream = new ByteArrayInputStream(bytes)) { try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream); ChatWallpaper wallpaper = WallpaperStorage.save(context, inputStream, "webp");
if (recipientId != null) { if (recipientId != null) {
Log.i(TAG, "Setting image wallpaper for " + recipientId); Log.i(TAG, "Setting image wallpaper for " + recipientId);
@@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.wallpaper.crop; package org.thoughtcrime.securesms.wallpaper.crop;
import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; 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 String EXTRA_RECIPIENT_ID = "RECIPIENT_ID";
private static final int CROP = 901; private static final int CROP = 901;
@RequiresPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
public static Intent getIntent(@NonNull Context context, public static Intent getIntent(@NonNull Context context,
@Nullable RecipientId recipientId) @Nullable RecipientId recipientId)
{ {
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/signal_background_secondary" />
<corners
android:radius="50dp" />
</shape>
@@ -9,6 +9,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/signal_background_primary"
tools:layout_height="200dp"> tools:layout_height="200dp">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@@ -55,6 +55,65 @@
app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_background" app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_background"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_navigation"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintStart_toStartOf="@id/chat_wallpaper_preview_top_bar"
app:srcCompat="@drawable/ic_arrow_left_24"
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_portrait"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="4dp"
android:layout_marginBottom="2dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintStart_toEndOf="@id/chat_wallpaper_preview_top_bar_navigation"
app:srcCompat="@drawable/circle_tintable"
app:tint="@color/core_white" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:text="@string/ChatWallpaperFragment__contact_name"
android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/core_white"
android:textSize="8sp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintEnd_toStartOf="@id/chat_wallpaper_preview_top_bar_video"
app:layout_constraintStart_toEndOf="@id/chat_wallpaper_preview_top_bar_portrait"
tools:ignore="SmallSp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_video"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintEnd_toStartOf="@id/chat_wallpaper_preview_top_bar_voice"
app:srcCompat="@drawable/ic_video_solid_24"
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_top_bar_voice"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="6dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_top_bar"
app:srcCompat="@drawable/ic_phone_right_solid_24"
app:tint="@color/core_white" />
<View <View
android:id="@+id/chat_wallpaper_preview_bottom_bar" android:id="@+id/chat_wallpaper_preview_bottom_bar"
android:layout_width="156dp" android:layout_width="156dp"
@@ -64,6 +123,33 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<View
android:id="@+id/chat_wallpaper_preview_bottom_bar_input"
android:layout_width="0dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:background="@drawable/chat_wallpaper_preview_input"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintEnd_toStartOf="@id/chat_wallpaper_preview_bottom_bar_plus"
app:layout_constraintStart_toStartOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_bottom_bar" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_bottom_bar_plus"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/circle_tintable"
android:padding="2dp"
app:backgroundTint="@color/signal_accent_primary"
app:layout_constraintBottom_toBottomOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_bottom_bar"
app:layout_constraintStart_toEndOf="@id/chat_wallpaper_preview_bottom_bar_input"
app:layout_constraintTop_toTopOf="@id/chat_wallpaper_preview_bottom_bar"
app:srcCompat="@drawable/ic_plus_24"
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/chat_wallpaper_preview_today" android:id="@+id/chat_wallpaper_preview_today"
android:layout_width="24dp" android:layout_width="24dp"
@@ -95,7 +181,7 @@
app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_background" app:layout_constraintEnd_toEndOf="@id/chat_wallpaper_preview_background"
app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_preview_top_bar" app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_preview_top_bar"
app:srcCompat="@drawable/chat_wallpaper_preview_bubble_10" app:srcCompat="@drawable/chat_wallpaper_preview_bubble_10"
app:tint="@color/signal_background_tertiary" /> app:tint="@color/signal_background_secondary" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
@@ -141,6 +227,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:visibility="gone"
tools:visibility="visible"
android:background="@color/signal_inverse_transparent_15" android:background="@color/signal_inverse_transparent_15"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -158,9 +246,11 @@
android:text="@string/ChatWallpaperFragment__clear_wallpaper" android:text="@string/ChatWallpaperFragment__clear_wallpaper"
android:textAppearance="@style/Signal.Text.Body" android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/signal_text_primary" android:textColor="@color/signal_text_primary"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_divider" /> app:layout_constraintTop_toBottomOf="@id/chat_wallpaper_divider"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/chat_wallpaper_reset_all_wallpapers" android:id="@+id/chat_wallpaper_reset_all_wallpapers"
@@ -65,14 +65,15 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/ChatWallpaperPreviewActivity__this_wallpaper_will_be_set_for_all_chats" android:maxWidth="194dp"
android:text="@string/ChatWallpaperPreviewActivity__swipe_to_preview_more_wallpapers"
android:textAppearance="@style/Signal.Text.Body" android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/core_white" /> android:textColor="@color/core_white" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/ChatWallpaperPreviewActivity__10_49_am" android:text="@string/DateUtils_just_now"
android:textAppearance="@style/Signal.Text.Caption" android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/transparent_white_80" /> android:textColor="@color/transparent_white_80" />
@@ -95,10 +96,12 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/preview_bubble_1"> app:layout_constraintTop_toBottomOf="@id/preview_bubble_1">
<TextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/preview_bubble_2_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/ChatWallpaperPreviewActivity__besides_those_you_manually_override" android:maxWidth="194dp"
android:text="@string/ChatWallpaperPreviewActivity__set_wallpaper_for_all_chats"
android:textAppearance="@style/Signal.Text.Body" android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/signal_text_primary" /> android:textColor="@color/signal_text_primary" />
@@ -107,7 +110,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:drawablePadding="4dp" android:drawablePadding="4dp"
android:text="@string/ChatWallpaperPreviewActivity__10_49_am" android:text="@string/DateUtils_just_now"
android:textAppearance="@style/Signal.Text.Caption" android:textAppearance="@style/Signal.Text.Caption"
android:textColor="@color/signal_text_secondary" android:textColor="@color/signal_text_secondary"
app:drawableEndCompat="@drawable/ic_delivery_status_read" app:drawableEndCompat="@drawable/ic_delivery_status_read"
@@ -8,14 +8,17 @@
android:background="@drawable/conversation_item_background" android:background="@drawable/conversation_item_background"
android:focusable="true" android:focusable="true"
android:paddingStart="26dp" android:paddingStart="26dp"
android:paddingEnd="26dp"> android:paddingEnd="26dp"
android:clipToPadding="false"
android:clipChildren="false">
<LinearLayout <LinearLayout
android:id="@+id/conversation_update_background" android:id="@+id/conversation_update_background"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginBottom="3dp" android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center" android:gravity="center"
android:paddingTop="8dp" android:paddingTop="8dp"
+9 -3
View File
@@ -2829,7 +2829,12 @@
<string name="ChatWallpaperFragment__set_wallpaper">Set wallpaper</string> <string name="ChatWallpaperFragment__set_wallpaper">Set wallpaper</string>
<string name="ChatWallpaperFragment__dark_theme_dims_wallpaper">Dark theme dims wallpaper</string> <string name="ChatWallpaperFragment__dark_theme_dims_wallpaper">Dark theme dims wallpaper</string>
<string name="ChatWallpaperFragment__clear_wallpaper">Clear wallpaper</string> <string name="ChatWallpaperFragment__clear_wallpaper">Clear wallpaper</string>
<string name="ChatWallpaperFragment__clear_wallpaper_question_mark">Clear wallpaper?</string>
<string name="ChatWallpaperFragment__reset_all_wallpapers">Reset all wallpapers</string> <string name="ChatWallpaperFragment__reset_all_wallpapers">Reset all wallpapers</string>
<string name="ChatWallpaperFragment__reset_all_wallpapers_question_mark">Reset all wallpapers?</string>
<string name="ChatWallpaperFragment__contact_name">Contact name</string>
<string name="ChatWallpaperFragment__reset">Reset</string>
<string name="ChatWallpaperFragment__clear">Clear</string>
<!-- ChatWallpaperSelectionFragment --> <!-- ChatWallpaperSelectionFragment -->
<string name="ChatWallpaperSelectionFragment__choose_from_photos">Choose from photos</string> <string name="ChatWallpaperSelectionFragment__choose_from_photos">Choose from photos</string>
@@ -2838,9 +2843,10 @@
<!-- ChatWallpaperPreviewActivity --> <!-- ChatWallpaperPreviewActivity -->
<string name="ChatWallpaperPreviewActivity__preview">Preview</string> <string name="ChatWallpaperPreviewActivity__preview">Preview</string>
<string name="ChatWallpaperPreviewActivity__set_wallpaper">Set wallpaper</string> <string name="ChatWallpaperPreviewActivity__set_wallpaper">Set wallpaper</string>
<string name="ChatWallpaperPreviewActivity__10_49_am">10:49 am</string> <string name="ChatWallpaperPreviewActivity__swipe_to_preview_more_wallpapers">Swipe to preview more wallpapers</string>
<string name="ChatWallpaperPreviewActivity__this_wallpaper_will_be_set_for_all_chats">This wallpaper will be set for all chats</string> <string name="ChatWallpaperPreviewActivity__set_wallpaper_for_all_chats">Set wallpaper for all chats</string>
<string name="ChatWallpaperPreviewActivity__besides_those_you_manually_override">Besides those you manually override</string> <string name="ChatWallpaperPreviewActivity__set_wallpaper_for_s">Set wallpaper for %1$s</string>
<string name="ChatWallpaperPreviewActivity__viewing_your_gallery_requires_the_storage_permission">Viewing your gallery requires the storage permission.</string>
<!-- WallpaperImageSelectionActivity --> <!-- WallpaperImageSelectionActivity -->
<string name="WallpaperImageSelectionActivity__choose_wallpaper_image">Choose wallpaper image</string> <string name="WallpaperImageSelectionActivity__choose_wallpaper_image">Choose wallpaper image</string>